[med-svn] [libundead] 01/02: New upstream version 1.0.6
Andreas Tille
tille at debian.org
Thu Mar 2 20:32:14 UTC 2017
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository libundead.
commit 299ed6fa191a9bd93add5a06d6411a2bb308fd49
Author: Andreas Tille <tille at debian.org>
Date: Thu Mar 2 21:30:18 2017 +0100
New upstream version 1.0.6
---
.editorconfig | 9 +
.gitignore | 33 +
.travis.yml | 15 +
LICENSE | 23 +
README.md | 13 +
dub.json | 9 +
posix.mak | 60 +
src/undead/bitarray.d | 942 ++++++++++++
src/undead/cstream.d | 249 ++++
src/undead/date.d | 1223 ++++++++++++++++
src/undead/datebase.d | 25 +
src/undead/dateparse.d | 784 ++++++++++
src/undead/doformat.d | 1620 +++++++++++++++++++++
src/undead/internal/file.d | 25 +
src/undead/metastrings.d | 218 +++
src/undead/regexp.d | 3439 ++++++++++++++++++++++++++++++++++++++++++++
src/undead/socketstream.d | 147 ++
src/undead/stream.d | 3071 +++++++++++++++++++++++++++++++++++++++
win32.mak | 55 +
19 files changed, 11960 insertions(+)
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f0ea6fb
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*.{c,h,d,di,dd}]
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+charset = utf-8
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..28cd1f4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+.B*
+*.bak
+*.lst
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a65f1a1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,15 @@
+language: d
+
+install:
+ - DMD_VER=2.066.1
+ - DUB_VER=0.9.22
+ - curl -fsSL http://downloads.dlang.org/releases/2014/dmd.${DMD_VER}.linux.zip > dmd.zip
+ - unzip -q -d ~ dmd.zip
+ - curl -fsSL http://code.dlang.org/files/dub-${DUB_VER}-linux-x86_64.tar.gz | tar -C ~/dmd2/linux/bin64 -xzf -
+ - export PATH="${HOME}/dmd2/linux/bin64:${PATH}"
+ - export LD_LIBRARY_PATH="${HOME}/dmd2/linux/lib64:${LD_LIBRARY_PATH}"
+
+script:
+ - dub test
+
+sudo: false
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/LICENSE
@@ -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/README.md b/README.md
new file mode 100644
index 0000000..6295604
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+undeaD
+======
+
+Need an obsolete Phobos module? Here they are, back from the dead and upgraded to work with the latest D
+
+Current modules included:
+
+* std.bitarray
+* std.date
+* std.datebase
+* std.dateparse
+* std.regexp
+* std.stream and friends
diff --git a/dub.json b/dub.json
new file mode 100644
index 0000000..4326926
--- /dev/null
+++ b/dub.json
@@ -0,0 +1,9 @@
+{
+ "name": "undead",
+ "description": "Obsolete Phobos modules, back from the dead",
+ "authors": ["various"],
+ "homepage": "https://github.com/DigitalMars/undead",
+ "license": "BSL-1.0",
+ "targetType": "library",
+ "targetPath": "bin",
+}
diff --git a/posix.mak b/posix.mak
new file mode 100644
index 0000000..05f0b35
--- /dev/null
+++ b/posix.mak
@@ -0,0 +1,60 @@
+#_ posix.mak
+# Build posix version of undead
+# Needs Digital Mars D compiler to build, available free from:
+# http://www.digitalmars.com/d/
+
+DMD=dmd
+DEL=rm
+S=src/undead
+O=obj
+B=bin
+
+TARGET=undead
+
+DFLAGS=-g -Isrc/
+LFLAGS=-L/map/co
+#DFLAGS=
+#LFLAGS=
+
+.d.obj :
+ $(DMD) -c $(DFLAGS) $*
+
+SRC= $S/bitarray.d $S/regexp.d $S/datebase.d $S/date.d $S/dateparse.d \
+ $S/cstream.d $S/stream.d $S/socketstream.d $S/doformat.d
+
+
+SOURCE= $(SRC) win32.mak posix.mak LICENSE README.md dub.json
+
+all: $B/$(TARGET).a
+
+#################################################
+
+$B/$(TARGET).a : $(SRC)
+ $(DMD) -lib -of$B/$(TARGET).a $(SRC) $(DFLAGS)
+
+
+unittest :
+ $(DMD) -unittest -main -cov -of$O/unittest $(SRC) $(DFLAGS)
+ $O/unittest
+
+
+clean:
+ $(DEL) $O/unittest *.lst
+
+
+tolf:
+ tolf $(SOURCE)
+
+
+detab:
+ detab $(SRC)
+
+
+zip: detab tolf $(SOURCE)
+ $(DEL) undead.zip
+ zip undead $(SOURCE)
+
+gitzip:
+ git archive --format=zip HEAD > undead.zip
+
+
diff --git a/src/undead/bitarray.d b/src/undead/bitarray.d
new file mode 100644
index 0000000..2ac264c
--- /dev/null
+++ b/src/undead/bitarray.d
@@ -0,0 +1,942 @@
+/***********************
+ * Source: $(PHOBOSSRC std/_bitarray.d)
+ * Macros:
+ * WIKI = StdBitarray
+ */
+
+module undead.bitarray;
+
+//debug = bitarray; // uncomment to turn on debugging printf's
+
+private import core.bitop;
+
+/**
+ * An array of bits.
+ */
+
+struct BitArray
+{
+ size_t len;
+ size_t* ptr;
+
+ size_t dim()
+ {
+ return (len + 31) / 32;
+ }
+
+ size_t length() const pure nothrow
+ {
+ return len;
+ }
+
+ void length(size_t newlen)
+ {
+ if (newlen != len)
+ {
+ size_t olddim = dim();
+ size_t newdim = (newlen + 31) / 32;
+
+ 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;
+ if (newdim & 31)
+ { // Set any pad bits to 0
+ ptr[newdim - 1] &= ~(~0 << (newdim & 31));
+ }
+ }
+
+ len = newlen;
+ }
+ }
+
+ /**********************************************
+ * Support for [$(I index)] operation for BitArray.
+ */
+ bool opIndex(size_t i)
+ in
+ {
+ assert(i < len);
+ }
+ body
+ {
+ return cast(bool)bt(ptr, i);
+ }
+
+ /** ditto */
+ bool opIndexAssign(bool b, size_t i)
+ in
+ {
+ assert(i < len);
+ }
+ body
+ {
+ if (b)
+ bts(ptr, i);
+ else
+ btr(ptr, i);
+ return b;
+ }
+
+ /**********************************************
+ * Support for array.dup property for BitArray.
+ */
+ BitArray dup()
+ {
+ BitArray ba;
+
+ auto b = ptr[0 .. dim].dup;
+ ba.len = len;
+ ba.ptr = b.ptr;
+ return ba;
+ }
+
+ unittest
+ {
+ BitArray a;
+ BitArray b;
+
+ 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 (int 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 foreach loops for BitArray.
+ */
+ int opApply(int delegate(ref bool) dg)
+ {
+ int result;
+
+ for (size_t i = 0; i < len; i++)
+ { bool b = opIndex(i);
+ result = dg(b);
+ (this)[i] = b;
+ if (result)
+ break;
+ }
+ return result;
+ }
+
+ /** ditto */
+ int opApply(int delegate(ref size_t, ref bool) dg)
+ {
+ int result;
+
+ for (size_t i = 0; i < len; i++)
+ { bool b = opIndex(i);
+ result = dg(i, b);
+ (this)[i] = b;
+ if (result)
+ break;
+ }
+ return result;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opApply unittest\n");
+
+ static bool[] ba = [1,0,1];
+
+ BitArray a; a.init(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);
+ }
+ }
+ }
+
+
+ /**********************************************
+ * Support for array.reverse property for BitArray.
+ */
+
+ BitArray reverse()
+ 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.init(data);
+ b.reverse;
+ for (i = 0; i < data.length; i++)
+ {
+ assert(b[i] == data[4 - i]);
+ }
+ }
+
+
+ /**********************************************
+ * Support for array.sort property for BitArray.
+ */
+
+ BitArray sort()
+ 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 BitArray ba = { 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 bit arrays.
+ */
+
+ bool opEquals(const ref BitArray a2) const pure nothrow
+ { size_t i;
+
+ if (this.length != a2.length)
+ return false; // not equal
+ byte *p1 = cast(byte*)this.ptr;
+ byte *p2 = cast(byte*)a2.ptr;
+ auto n = this.length / 8;
+ for (i = 0; i < n; i++)
+ {
+ if (p1[i] != p2[i])
+ return false; // not equal
+ }
+
+ n = this.length & 7;
+ auto mask = cast(ubyte)((1 << n) - 1);
+ //printf("i = %d, n = %d, mask = %x, %x, %x\n", i, n, mask, p1[i], p2[i]);
+ return (mask == 0) || (p1[i] & mask) == (p2[i] & mask);
+ }
+
+ 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];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(bb);
+ BitArray c; c.init(bc);
+ BitArray d; d.init(bd);
+ BitArray e; e.init(be);
+
+ assert(a != b);
+ assert(a != c);
+ assert(a != d);
+ assert(a == e);
+ }
+
+ /***************************************
+ * Implement comparison operators.
+ */
+
+ int opCmp(const ref BitArray a2) const pure nothrow
+ {
+ size_t i;
+
+ auto len = this.length;
+ if (a2.length < len)
+ len = a2.length;
+ auto p1 = cast(ubyte*)this.ptr;
+ auto p2 = cast(ubyte*)a2.ptr;
+ auto n = len / 8;
+ for (i = 0; i < n; i++)
+ {
+ if (p1[i] != p2[i])
+ break; // not equal
+ }
+ for (auto j = i * 8; j < len; j++)
+ { auto mask = cast(ubyte)(1 << j);
+
+ auto c = cast(int)(p1[i] & mask) - cast(int)(p2[i] & mask);
+ if (c)
+ return c;
+ }
+ version (D_LP64)
+ {
+ long c = this.len - a2.length;
+ if (c < 0)
+ return -1;
+ else
+ return c != 0;
+ }
+ else
+ return cast(int)this.len - cast(int)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];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(bb);
+ BitArray c; c.init(bc);
+ BitArray d; d.init(bd);
+ BitArray e; e.init(be);
+
+ 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);
+ }
+
+ /***************************************
+ * Set BitArray to contents of ba[]
+ */
+
+ void init(bool[] ba)
+ {
+ length = ba.length;
+ foreach (i, b; ba)
+ {
+ (this)[i] = b;
+ }
+ }
+
+
+ /***************************************
+ * Map BitArray onto v[], with numbits being the number of bits
+ * in the array. Does not copy the data.
+ *
+ * This is the inverse of opCast.
+ */
+ void init(void[] v, size_t numbits)
+ in
+ {
+ assert(numbits <= v.length * 8);
+ assert((v.length & 3) == 0);
+ }
+ body
+ {
+ ptr = cast(typeof(ptr))v.ptr;
+ len = numbits;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.init unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+
+ BitArray a; a.init(ba);
+ BitArray b;
+ void[] v;
+
+ v = cast(void[])a;
+ b.init(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 void[].
+ */
+ void[] opCast()
+ {
+ return cast(void[])ptr[0 .. dim];
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opCast unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+
+ BitArray a; a.init(ba);
+ void[] v = cast(void[])a;
+
+ assert(v.length == a.dim * size_t.sizeof);
+ }
+
+ /***************************************
+ * Support for unary operator ~ for bit arrays.
+ */
+ BitArray opCom()
+ {
+ auto dim = this.dim();
+
+ BitArray result;
+
+ result.length = len;
+ for (size_t i = 0; i < dim; i++)
+ result.ptr[i] = ~this.ptr[i];
+ if (len & 31)
+ result.ptr[dim - 1] &= ~(~0 << (len & 31));
+ return result;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opCom unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+
+ BitArray a; a.init(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 operator & for bit arrays.
+ */
+ BitArray opAnd(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ BitArray result;
+
+ result.length = len;
+ for (size_t i = 0; i < dim; i++)
+ result.ptr[i] = this.ptr[i] & e2.ptr[i];
+ 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];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(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);
+ }
+
+
+ /***************************************
+ * Support for binary operator | for bit arrays.
+ */
+ BitArray opOr(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ BitArray result;
+
+ result.length = len;
+ for (size_t i = 0; i < dim; i++)
+ result.ptr[i] = this.ptr[i] | e2.ptr[i];
+ return result;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opOr unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+ static bool[] bb = [1,0,1,1,0];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(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);
+ }
+
+
+ /***************************************
+ * Support for binary operator ^ for bit arrays.
+ */
+ BitArray opXor(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ BitArray result;
+
+ result.length = len;
+ for (size_t i = 0; i < dim; i++)
+ result.ptr[i] = this.ptr[i] ^ e2.ptr[i];
+ return result;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opXor unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+ static bool[] bb = [1,0,1,1,0];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(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);
+ }
+
+
+ /***************************************
+ * Support for binary operator - for bit arrays.
+ *
+ * $(I a - b) for BitArrays means the same thing as $(I a & ~b).
+ */
+ BitArray opSub(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ BitArray result;
+
+ result.length = len;
+ for (size_t i = 0; i < dim; i++)
+ result.ptr[i] = this.ptr[i] & ~e2.ptr[i];
+ return result;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opSub unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+ static bool[] bb = [1,0,1,1,0];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(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 &= bit arrays.
+ */
+ BitArray opAndAssign(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ for (size_t i = 0; i < dim; i++)
+ ptr[i] &= e2.ptr[i];
+ return this;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opAndAssign unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+ static bool[] bb = [1,0,1,1,0];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(bb);
+
+ a &= b;
+ assert(a[0] == 1);
+ assert(a[1] == 0);
+ assert(a[2] == 1);
+ assert(a[3] == 0);
+ assert(a[4] == 0);
+ }
+
+
+ /***************************************
+ * Support for operator |= for bit arrays.
+ */
+ BitArray opOrAssign(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ for (size_t i = 0; i < dim; i++)
+ ptr[i] |= e2.ptr[i];
+ return this;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opOrAssign unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+ static bool[] bb = [1,0,1,1,0];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(bb);
+
+ a |= b;
+ assert(a[0] == 1);
+ assert(a[1] == 0);
+ assert(a[2] == 1);
+ assert(a[3] == 1);
+ assert(a[4] == 1);
+ }
+
+ /***************************************
+ * Support for operator ^= for bit arrays.
+ */
+ BitArray opXorAssign(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ for (size_t i = 0; i < dim; i++)
+ ptr[i] ^= e2.ptr[i];
+ return this;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opXorAssign unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+ static bool[] bb = [1,0,1,1,0];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(bb);
+
+ a ^= b;
+ assert(a[0] == 0);
+ assert(a[1] == 0);
+ assert(a[2] == 0);
+ assert(a[3] == 1);
+ assert(a[4] == 1);
+ }
+
+ /***************************************
+ * Support for operator -= for bit arrays.
+ *
+ * $(I a -= b) for BitArrays means the same thing as $(I a &= ~b).
+ */
+ BitArray opSubAssign(BitArray e2)
+ in
+ {
+ assert(len == e2.length);
+ }
+ body
+ {
+ auto dim = this.dim();
+
+ for (size_t i = 0; i < dim; i++)
+ ptr[i] &= ~e2.ptr[i];
+ return this;
+ }
+
+ unittest
+ {
+ debug(bitarray) printf("BitArray.opSubAssign unittest\n");
+
+ static bool[] ba = [1,0,1,0,1];
+ static bool[] bb = [1,0,1,1,0];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(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 bit arrays.
+ */
+
+ BitArray opCatAssign(bool b)
+ {
+ 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];
+
+ BitArray a; a.init(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)
+ {
+ 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];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(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 bit arrays.
+ */
+ BitArray opCat(bool b)
+ {
+ auto r = this.dup;
+ r.length = len + 1;
+ r[len] = b;
+ return r;
+ }
+
+ /** ditto */
+ BitArray opCat_r(bool b)
+ {
+ BitArray r;
+
+ r.length = len + 1;
+ r[0] = b;
+ for (size_t i = 0; i < len; i++)
+ r[1 + i] = (this)[i];
+ return r;
+ }
+
+ /** ditto */
+ BitArray opCat(BitArray b)
+ {
+ 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];
+
+ BitArray a; a.init(ba);
+ BitArray b; b.init(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);
+ }
+}
diff --git a/src/undead/cstream.d b/src/undead/cstream.d
new file mode 100644
index 0000000..14ffaea
--- /dev/null
+++ b/src/undead/cstream.d
@@ -0,0 +1,249 @@
+// Written in the D programming language.
+
+/**
+ * $(RED Deprecated: This module is considered out-dated and not up to Phobos'
+ * current standards.)
+ *
+ * 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)
+ */
+module undead.cstream;
+
+public import core.stdc.stdio;
+public import undead.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 undead.internal.file : deleteme;
+ import std.internal.cstring : tempCString;
+
+ auto stream_file = (undead.internal.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/src/undead/date.d b/src/undead/date.d
new file mode 100644
index 0000000..663289b
--- /dev/null
+++ b/src/undead/date.d
@@ -0,0 +1,1223 @@
+// Written in the D programming language.
+
+/**
+ * $(RED Deprecated. It will be removed in February 2012.
+ * Please use std.datetime instead.)
+ *
+ * Dates are represented in several formats. The date implementation
+ * revolves around a central type, $(D d_time), from which other
+ * formats are converted to and from. Dates are calculated using the
+ * Gregorian calendar.
+ *
+ * References: $(WEB wikipedia.org/wiki/Gregorian_calendar, Gregorian
+ * calendar (Wikipedia))
+ *
+ * Macros: WIKI = Phobos/StdDate
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
+ * Authors: $(WEB digitalmars.com, Walter Bright)
+ * Source: $(PHOBOSSRC std/_date.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 undead.date;
+
+import std.conv, std.exception, std.stdio;
+import core.stdc.stdlib;
+
+import undead.datebase;
+import undead.dateparse;
+
+/+
+pragma(msg, "Notice: As of Phobos 2.055, std.date and std.dateparse have been " ~
+ "deprecated. They will be removed in February 2012. " ~
+ "Please use std.datetime instead.");
+
+deprecated:
++/
+
+/**
+ * $(D d_time) is a signed arithmetic type giving the time elapsed
+ * since January 1, 1970. Negative values are for dates preceding
+ * 1970. The time unit used is Ticks. Ticks are milliseconds or
+ * smaller intervals.
+ *
+ * The usual arithmetic operations can be performed on d_time, such as adding,
+ * subtracting, etc. Elapsed time in Ticks can be computed by subtracting a
+ * starting d_time from an ending d_time.
+ */
+alias long d_time;
+
+/**
+ * A value for d_time that does not represent a valid time.
+ */
+enum d_time d_time_nan = long.min;
+
+/**
+ * Time broken down into its components.
+ */
+struct Date
+{
+ int year = int.min; /// use int.min as "nan" year value
+ int month; /// 1..12
+ int day; /// 1..31
+ int hour; /// 0..23
+ int minute; /// 0..59
+ int second; /// 0..59
+ int ms; /// 0..999
+ int weekday; /// 0: not specified, 1..7: Sunday..Saturday
+ int tzcorrection = int.min; /// -1200..1200 correction in hours
+
+ /// Parse date out of string s[] and store it in this Date instance.
+ void parse(string s)
+ {
+ DateParse dp;
+ dp.parse(s, this);
+ }
+}
+
+enum
+{
+ hoursPerDay = 24,
+ minutesPerHour = 60,
+ msPerMinute = 60 * 1000,
+ msPerHour = 60 * msPerMinute,
+ msPerDay = 86_400_000,
+ ticksPerMs = 1,
+ ticksPerSecond = 1000, /// Will be at least 1000
+ ticksPerMinute = ticksPerSecond * 60,
+ ticksPerHour = ticksPerMinute * 60,
+ ticksPerDay = ticksPerHour * 24,
+}
+
+deprecated alias ticksPerSecond TicksPerSecond;
+deprecated alias ticksPerMs TicksPerMs;
+deprecated alias ticksPerMinute TicksPerMinute;
+deprecated alias ticksPerHour TicksPerHour;
+deprecated alias ticksPerDay TicksPerDay;
+
+deprecated
+unittest
+{
+ assert(ticksPerSecond == TicksPerSecond);
+}
+
+__gshared d_time localTZA = 0;
+
+private immutable char[] daystr = "SunMonTueWedThuFriSat";
+private immutable char[] monstr = "JanFebMarAprMayJunJulAugSepOctNovDec";
+
+private immutable int[12] mdays =
+ [ 0,31,59,90,120,151,181,212,243,273,304,334 ];
+
+/********************************
+ * Compute year and week [1..53] from t. The ISO 8601 week 1 is the first week
+ * of the year that includes January 4. Monday is the first day of the week.
+ * References:
+ * $(LINK2 http://en.wikipedia.org/wiki/ISO_8601, ISO 8601 (Wikipedia))
+ */
+
+void toISO8601YearWeek(d_time t, out int year, out int week)
+{
+ year = yearFromTime(t);
+
+ auto yday = day(t) - dayFromYear(year);
+
+ /* Determine day of week Jan 4 falls on.
+ * Weeks begin on a Monday.
+ */
+
+ auto d = dayFromYear(year);
+ auto w = (d + 3/*Jan4*/ + 3) % 7;
+ if (w < 0)
+ w += 7;
+
+ /* Find yday of beginning of ISO 8601 year
+ */
+ auto ydaybeg = 3/*Jan4*/ - w;
+
+ /* Check if yday is actually the last week of the previous year
+ */
+ if (yday < ydaybeg)
+ {
+ year -= 1;
+ week = 53;
+ return;
+ }
+
+ /* Check if yday is actually the first week of the next year
+ */
+ if (yday >= 362) // possible
+ { int d2;
+ int ydaybeg2;
+
+ d2 = dayFromYear(year + 1);
+ w = (d2 + 3/*Jan4*/ + 3) % 7;
+ if (w < 0)
+ w += 7;
+ //printf("w = %d\n", w);
+ ydaybeg2 = 3/*Jan4*/ - w;
+ if (d + yday >= d2 + ydaybeg2)
+ {
+ year += 1;
+ week = 1;
+ return;
+ }
+ }
+
+ week = (yday - ydaybeg) / 7 + 1;
+}
+
+/* ***********************************
+ * Divide time by divisor. Always round down, even if d is negative.
+ */
+
+pure d_time floor(d_time d, int divisor)
+{
+ return (d < 0 ? d - divisor - 1 : d) / divisor;
+}
+
+int dmod(d_time n, d_time d)
+{ d_time r;
+
+ r = n % d;
+ if (r < 0)
+ r += d;
+ assert(cast(int)r == r);
+ return cast(int)r;
+}
+
+/********************************
+ * Calculates the hour from time.
+ *
+ * Params:
+ * time = The time to compute the hour from.
+ * Returns:
+ * The calculated hour, 0..23.
+ */
+int hourFromTime(d_time time)
+{
+ return dmod(floor(time, msPerHour), hoursPerDay);
+}
+
+/********************************
+ * Calculates the minute from time.
+ *
+ * Params:
+ * time = The time to compute the minute from.
+ * Returns:
+ * The calculated minute, 0..59.
+ */
+int minFromTime(d_time time)
+{
+ return dmod(floor(time, msPerMinute), minutesPerHour);
+}
+
+/********************************
+ * Calculates the second from time.
+ *
+ * Params:
+ * time = The time to compute the second from.
+ * Returns:
+ * The calculated second, 0..59.
+ */
+int secFromTime(d_time time)
+{
+ return dmod(floor(time, ticksPerSecond), 60);
+}
+
+/********************************
+ * Calculates the milisecond from time.
+ *
+ * Params:
+ * time = The time to compute the milisecond from.
+ * Returns:
+ * The calculated milisecond, 0..999.
+ */
+int msFromTime(d_time time)
+{
+ return dmod(time / (ticksPerSecond / 1000), 1000);
+}
+
+int timeWithinDay(d_time t)
+{
+ return dmod(t, msPerDay);
+}
+
+d_time toInteger(d_time n)
+{
+ return n;
+}
+
+int day(d_time t)
+{
+ return cast(int)floor(t, msPerDay);
+}
+
+pure bool leapYear(uint y)
+{
+ return (y % 4) == 0 && (y % 100 || (y % 400) == 0);
+}
+
+unittest {
+ assert(!leapYear(1970));
+ assert(leapYear(1984));
+ assert(leapYear(2000));
+ assert(!leapYear(2100));
+}
+
+/********************************
+ * Calculates the number of days that exists in a year.
+ *
+ * Leap years have 366 days, while other years have 365.
+ *
+ * Params:
+ * year = The year to compute the number of days from.
+ * Returns:
+ * The number of days in the year, 365 or 366.
+ */
+pure uint daysInYear(uint year)
+{
+ return (leapYear(year) ? 366 : 365);
+}
+
+
+/********************************
+ * Calculates the number of days elapsed since 1 January 1970
+ * until 1 January of the given year.
+ *
+ * Params:
+ * year = The year to compute the number of days from.
+ * Returns:
+ * The number of days elapsed.
+ *
+ * Example:
+ * ----------
+ * writeln(dayFromYear(1970)); // writes '0'
+ * writeln(dayFromYear(1971)); // writes '365'
+ * writeln(dayFromYear(1972)); // writes '730'
+ * ----------
+ */
+pure int dayFromYear(int year)
+{
+ return cast(int) (365 * (year - 1970) +
+ floor((year - 1969), 4) -
+ floor((year - 1901), 100) +
+ floor((year - 1601), 400));
+}
+
+pure d_time timeFromYear(int y)
+{
+ return cast(d_time)msPerDay * dayFromYear(y);
+}
+
+/*****************************
+ * Calculates the year from the d_time t.
+ */
+
+pure int yearFromTime(d_time t)
+{
+
+ if (t == d_time_nan)
+ return 0;
+
+ // Hazard a guess
+ //y = 1970 + cast(int) (t / (365.2425 * msPerDay));
+ // Use integer only math
+ int y = 1970 + cast(int) (t / (3652425 * (msPerDay / 10000)));
+
+ if (timeFromYear(y) <= t)
+ {
+ while (timeFromYear(y + 1) <= t)
+ y++;
+ }
+ else
+ {
+ do
+ {
+ y--;
+ }
+ while (timeFromYear(y) > t);
+ }
+ return y;
+}
+
+/*******************************
+ * Determines if d_time t is a leap year.
+ *
+ * A leap year is every 4 years except years ending in 00 that are not
+ * divsible by 400.
+ *
+ * Returns: !=0 if it is a leap year.
+ *
+ * References:
+ * $(LINK2 http://en.wikipedia.org/wiki/Leap_year, Wikipedia)
+ */
+
+pure bool inLeapYear(d_time t)
+{
+ return leapYear(yearFromTime(t));
+}
+
+/*****************************
+ * Calculates the month from the d_time t.
+ *
+ * Returns: Integer in the range 0..11, where
+ * 0 represents January and 11 represents December.
+ */
+
+int monthFromTime(d_time t)
+{
+ auto year = yearFromTime(t);
+ auto day = day(t) - dayFromYear(year);
+
+ int month;
+ if (day < 59)
+ {
+ if (day < 31)
+ { assert(day >= 0);
+ month = 0;
+ }
+ else
+ month = 1;
+ }
+ else
+ {
+ day -= leapYear(year);
+ if (day < 212)
+ {
+ if (day < 59)
+ month = 1;
+ else if (day < 90)
+ month = 2;
+ else if (day < 120)
+ month = 3;
+ else if (day < 151)
+ month = 4;
+ else if (day < 181)
+ month = 5;
+ else
+ month = 6;
+ }
+ else
+ {
+ if (day < 243)
+ month = 7;
+ else if (day < 273)
+ month = 8;
+ else if (day < 304)
+ month = 9;
+ else if (day < 334)
+ month = 10;
+ else if (day < 365)
+ month = 11;
+ else
+ assert(0);
+ }
+ }
+ return month;
+}
+
+/*******************************
+ * Compute which day in a month a d_time t is.
+ * Returns:
+ * Integer in the range 1..31
+ */
+int dateFromTime(d_time t)
+{
+ auto year = yearFromTime(t);
+ auto day = day(t) - dayFromYear(year);
+ auto leap = leapYear(year);
+ auto month = monthFromTime(t);
+ int date;
+ switch (month)
+ {
+ case 0: date = day + 1; break;
+ case 1: date = day - 30; break;
+ case 2: date = day - 58 - leap; break;
+ case 3: date = day - 89 - leap; break;
+ case 4: date = day - 119 - leap; break;
+ case 5: date = day - 150 - leap; break;
+ case 6: date = day - 180 - leap; break;
+ case 7: date = day - 211 - leap; break;
+ case 8: date = day - 242 - leap; break;
+ case 9: date = day - 272 - leap; break;
+ case 10: date = day - 303 - leap; break;
+ case 11: date = day - 333 - leap; break;
+ default:
+ assert(0);
+ }
+ return date;
+}
+
+/*******************************
+ * Compute which day of the week a d_time t is.
+ * Returns:
+ * Integer in the range 0..6, where 0 represents Sunday
+ * and 6 represents Saturday.
+ */
+int weekDay(d_time t)
+{
+ auto w = (cast(int)day(t) + 4) % 7;
+ if (w < 0)
+ w += 7;
+ return w;
+}
+
+/***********************************
+ * Convert from UTC to local time.
+ */
+
+d_time UTCtoLocalTime(d_time t)
+{
+ return (t == d_time_nan)
+ ? d_time_nan
+ : t + localTZA + daylightSavingTA(t);
+}
+
+/***********************************
+ * Convert from local time to UTC.
+ */
+
+d_time localTimetoUTC(d_time t)
+{
+ return (t == d_time_nan)
+ ? d_time_nan
+/* BUGZILLA 1752 says this line should be:
+ * : t - localTZA - daylightSavingTA(t);
+ */
+ : t - localTZA - daylightSavingTA(t - localTZA);
+}
+
+
+d_time makeTime(d_time hour, d_time min, d_time sec, d_time ms)
+{
+ return hour * ticksPerHour +
+ min * ticksPerMinute +
+ sec * ticksPerSecond +
+ ms * ticksPerMs;
+}
+
+/* *****************************
+ * Params:
+ * month = 0..11
+ * date = day of month, 1..31
+ * Returns:
+ * number of days since start of epoch
+ */
+
+d_time makeDay(d_time year, d_time month, d_time date)
+{
+ const y = cast(int)(year + floor(month, 12));
+ const m = dmod(month, 12);
+
+ const leap = leapYear(y);
+ auto t = timeFromYear(y) + cast(d_time) mdays[m] * msPerDay;
+ if (leap && month >= 2)
+ t += msPerDay;
+
+ if (yearFromTime(t) != y ||
+ monthFromTime(t) != m ||
+ dateFromTime(t) != 1)
+ {
+ return d_time_nan;
+ }
+
+ return day(t) + date - 1;
+}
+
+d_time makeDate(d_time day, d_time time)
+{
+ if (day == d_time_nan || time == d_time_nan)
+ return d_time_nan;
+
+ return day * ticksPerDay + time;
+}
+
+d_time timeClip(d_time time)
+{
+ //printf("TimeClip(%g) = %g\n", time, toInteger(time));
+
+ return toInteger(time);
+}
+
+/***************************************
+ * Determine the date in the month, 1..31, of the nth
+ * weekday.
+ * Params:
+ * year = year
+ * month = month, 1..12
+ * weekday = day of week 0..6 representing Sunday..Saturday
+ * n = nth occurrence of that weekday in the month, 1..5, where
+ * 5 also means "the last occurrence in the month"
+ * Returns:
+ * the date in the month, 1..31, of the nth weekday
+ */
+
+int dateFromNthWeekdayOfMonth(int year, int month, int weekday, int n)
+in
+{
+ assert(1 <= month && month <= 12);
+ assert(0 <= weekday && weekday <= 6);
+ assert(1 <= n && n <= 5);
+}
+body
+{
+ // Get day of the first of the month
+ auto x = makeDay(year, month - 1, 1);
+
+ // Get the week day 0..6 of the first of this month
+ auto wd = weekDay(makeDate(x, 0));
+
+ // Get monthday of first occurrence of weekday in this month
+ auto mday = weekday - wd + 1;
+ if (mday < 1)
+ mday += 7;
+
+ // Add in number of weeks
+ mday += (n - 1) * 7;
+
+ // If monthday is more than the number of days in the month,
+ // back up to 'last' occurrence
+ if (mday > 28 && mday > daysInMonth(year, month))
+ { assert(n == 5);
+ mday -= 7;
+ }
+
+ return mday;
+}
+
+unittest
+{
+ assert(dateFromNthWeekdayOfMonth(2003, 3, 0, 5) == 30);
+ assert(dateFromNthWeekdayOfMonth(2003, 10, 0, 5) == 26);
+ assert(dateFromNthWeekdayOfMonth(2004, 3, 0, 5) == 28);
+ assert(dateFromNthWeekdayOfMonth(2004, 10, 0, 5) == 31);
+}
+
+/**************************************
+ * Determine the number of days in a month, 1..31.
+ * Params:
+ * month = 1..12
+ */
+
+int daysInMonth(int year, int month)
+{
+ switch (month)
+ {
+ case 1:
+ case 3:
+ case 5:
+ case 7:
+ case 8:
+ case 10:
+ case 12:
+ return 31;
+ case 2:
+ return 28 + leapYear(year);
+ case 4:
+ case 6:
+ case 9:
+ case 11:
+ return 30;
+ default:
+ break;
+ }
+ return enforce(false, "Invalid month passed to daysInMonth");
+}
+
+unittest
+{
+ assert(daysInMonth(2003, 2) == 28);
+ assert(daysInMonth(2004, 2) == 29);
+}
+
+/*************************************
+ * Converts UTC time into a text string of the form:
+ * "Www Mmm dd hh:mm:ss GMT+-TZ yyyy".
+ * For example, "Tue Apr 02 02:04:57 GMT-0800 1996".
+ * If time is invalid, i.e. is d_time_nan,
+ * the string "Invalid date" is returned.
+ *
+ * Example:
+ * ------------------------------------
+ d_time lNow;
+ char[] lNowString;
+
+ // Grab the date and time relative to UTC
+ lNow = std.date.getUTCtime();
+ // Convert this into the local date and time for display.
+ lNowString = std.date.UTCtoString(lNow);
+ * ------------------------------------
+ */
+
+string UTCtoString(d_time time)
+{
+ // Years are supposed to be -285616 .. 285616, or 7 digits
+ // "Tue Apr 02 02:04:57 GMT-0800 1996"
+ auto buffer = new char[29 + 7 + 1];
+
+ if (time == d_time_nan)
+ return "Invalid Date";
+
+ auto dst = daylightSavingTA(time);
+ auto offset = localTZA + dst;
+ auto t = time + offset;
+ auto sign = '+';
+ if (offset < 0)
+ { sign = '-';
+// offset = -offset;
+ offset = -(localTZA + dst);
+ }
+
+ auto mn = cast(int)(offset / msPerMinute);
+ auto hr = mn / 60;
+ mn %= 60;
+
+ //printf("hr = %d, offset = %g, localTZA = %g, dst = %g, + = %g\n", hr, offset, localTZA, dst, localTZA + dst);
+
+ auto len = sprintf(buffer.ptr,
+ "%.3s %.3s %02d %02d:%02d:%02d GMT%c%02d%02d %d",
+ &daystr[weekDay(t) * 3],
+ &monstr[monthFromTime(t) * 3],
+ dateFromTime(t),
+ hourFromTime(t), minFromTime(t), secFromTime(t),
+ sign, hr, mn,
+ cast(long)yearFromTime(t));
+
+ // Ensure no buggy buffer overflows
+ //printf("len = %d, buffer.length = %d\n", len, buffer.length);
+ assert(len < buffer.length);
+ buffer = buffer[0 .. len];
+ return assumeUnique(buffer);
+}
+
+/// Alias for UTCtoString (deprecated).
+deprecated alias UTCtoString toString;
+
+/***********************************
+ * Converts t into a text string of the form: "Www, dd Mmm yyyy hh:mm:ss UTC".
+ * If t is invalid, "Invalid date" is returned.
+ */
+
+string toUTCString(d_time t)
+{
+ // Years are supposed to be -285616 .. 285616, or 7 digits
+ // "Tue, 02 Apr 1996 02:04:57 GMT"
+ auto buffer = new char[25 + 7 + 1];
+
+ if (t == d_time_nan)
+ return "Invalid Date";
+
+ auto len = sprintf(buffer.ptr, "%.3s, %02d %.3s %d %02d:%02d:%02d UTC",
+ &daystr[weekDay(t) * 3], dateFromTime(t),
+ &monstr[monthFromTime(t) * 3],
+ yearFromTime(t),
+ hourFromTime(t), minFromTime(t), secFromTime(t));
+
+ // Ensure no buggy buffer overflows
+ assert(len < buffer.length);
+
+ return cast(string) buffer[0 .. len];
+}
+
+/************************************
+ * Converts the date portion of time into a text string of the form: "Www Mmm dd
+ * yyyy", for example, "Tue Apr 02 1996".
+ * If time is invalid, "Invalid date" is returned.
+ */
+
+string toDateString(d_time time)
+{
+ // Years are supposed to be -285616 .. 285616, or 7 digits
+ // "Tue Apr 02 1996"
+ auto buffer = new char[29 + 7 + 1];
+
+ if (time == d_time_nan)
+ return "Invalid Date";
+
+ auto dst = daylightSavingTA(time);
+ auto offset = localTZA + dst;
+ auto t = time + offset;
+
+ auto len = sprintf(buffer.ptr, "%.3s %.3s %02d %d",
+ &daystr[weekDay(t) * 3],
+ &monstr[monthFromTime(t) * 3],
+ dateFromTime(t),
+ cast(long)yearFromTime(t));
+
+ // Ensure no buggy buffer overflows
+ assert(len < buffer.length);
+
+ return cast(string) buffer[0 .. len];
+}
+
+/******************************************
+ * Converts the time portion of t into a text string of the form: "hh:mm:ss
+ * GMT+-TZ", for example, "02:04:57 GMT-0800".
+ * If t is invalid, "Invalid date" is returned.
+ * The input must be in UTC, and the output is in local time.
+ */
+
+string toTimeString(d_time time)
+{
+ // "02:04:57 GMT-0800"
+ auto buffer = new char[17 + 1];
+
+ if (time == d_time_nan)
+ return "Invalid Date";
+
+ auto dst = daylightSavingTA(time);
+ auto offset = localTZA + dst;
+ auto t = time + offset;
+ auto sign = '+';
+ if (offset < 0)
+ { sign = '-';
+// offset = -offset;
+ offset = -(localTZA + dst);
+ }
+
+ auto mn = cast(int)(offset / msPerMinute);
+ auto hr = mn / 60;
+ mn %= 60;
+
+ //printf("hr = %d, offset = %g, localTZA = %g, dst = %g, + = %g\n", hr, offset, localTZA, dst, localTZA + dst);
+
+ auto len = sprintf(buffer.ptr, "%02d:%02d:%02d GMT%c%02d%02d",
+ hourFromTime(t), minFromTime(t), secFromTime(t),
+ sign, hr, mn);
+
+ // Ensure no buggy buffer overflows
+ assert(len < buffer.length);
+
+ // Lop off terminating 0
+ return cast(string) buffer[0 .. len];
+}
+
+
+/******************************************
+ * Parses s as a textual date string, and returns it as a d_time. If
+ * the string is not a valid date, $(D d_time_nan) is returned.
+ */
+
+d_time parse(string s)
+{
+ try
+ {
+ Date dp;
+ dp.parse(s);
+ auto time = makeTime(dp.hour, dp.minute, dp.second, dp.ms);
+ // Assume UTC if no tzcorrection is set (runnable/testdate).
+ if (dp.tzcorrection != int.min)
+ {
+ time += cast(d_time)(dp.tzcorrection / 100) * msPerHour +
+ cast(d_time)(dp.tzcorrection % 100) * msPerMinute;
+ }
+ auto day = makeDay(dp.year, dp.month - 1, dp.day);
+ auto result = makeDate(day,time);
+ return timeClip(result);
+ }
+ catch
+ {
+ return d_time_nan; // erroneous date string
+ }
+}
+
+extern(C) void std_date_static_this()
+{
+ localTZA = getLocalTZA();
+}
+
+version (Win32)
+{
+ private import core.sys.windows.windows;
+ //import c.time;
+
+ /******
+ * Get current UTC time.
+ */
+ d_time getUTCtime()
+ {
+ SYSTEMTIME st;
+ GetSystemTime(&st); // get time in UTC
+ return SYSTEMTIME2d_time(&st, 0);
+ //return c.time.time(null) * ticksPerSecond;
+ }
+
+ static d_time FILETIME2d_time(const FILETIME *ft)
+ {
+ SYSTEMTIME st = void;
+ if (!FileTimeToSystemTime(ft, &st))
+ return d_time_nan;
+ return SYSTEMTIME2d_time(&st, 0);
+ }
+
+ FILETIME d_time2FILETIME(d_time dt)
+ {
+ static assert(10_000_000 >= ticksPerSecond);
+ static assert(10_000_000 % ticksPerSecond == 0);
+ enum ulong ticksFrom1601To1970 = 11_644_473_600UL * ticksPerSecond;
+ ulong t = (dt + ticksFrom1601To1970) * (10_000_000 / ticksPerSecond);
+ FILETIME result = void;
+ result.dwLowDateTime = cast(uint) (t & uint.max);
+ result.dwHighDateTime = cast(uint) (t >> 32);
+ return result;
+ }
+
+ unittest
+ {
+ auto dt = getUTCtime();
+ auto ft = d_time2FILETIME(dt);
+ auto dt1 = FILETIME2d_time(&ft);
+ assert(dt == dt1, text(dt, " != ", dt1));
+ }
+
+ static d_time SYSTEMTIME2d_time(const SYSTEMTIME *st, d_time t)
+ {
+ /* More info: http://delphicikk.atw.hu/listaz.php?id=2667&oldal=52
+ */
+ d_time day = void;
+ d_time time = void;
+
+ if (st.wYear)
+ {
+ time = makeTime(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
+ day = makeDay(st.wYear, st.wMonth - 1, st.wDay);
+ }
+ else
+ { /* wYear being 0 is a flag to indicate relative time:
+ * wMonth is the month 1..12
+ * wDayOfWeek is weekday 0..6 corresponding to Sunday..Saturday
+ * wDay is the nth time, 1..5, that wDayOfWeek occurs
+ */
+
+ auto year = yearFromTime(t);
+ auto mday = dateFromNthWeekdayOfMonth(year,
+ st.wMonth, st.wDay, st.wDayOfWeek);
+ day = makeDay(year, st.wMonth - 1, mday);
+ time = makeTime(st.wHour, st.wMinute, 0, 0);
+ }
+ auto n = makeDate(day,time);
+ return timeClip(n);
+ }
+
+ d_time getLocalTZA()
+ {
+ TIME_ZONE_INFORMATION tzi = void;
+
+ /* http://msdn.microsoft.com/library/en-us/sysinfo/base/gettimezoneinformation.asp
+ * http://msdn2.microsoft.com/en-us/library/ms725481.aspx
+ */
+ auto r = GetTimeZoneInformation(&tzi);
+ //printf("bias = %d\n", tzi.Bias);
+ //printf("standardbias = %d\n", tzi.StandardBias);
+ //printf("daylightbias = %d\n", tzi.DaylightBias);
+ switch (r)
+ {
+ case TIME_ZONE_ID_STANDARD:
+ return -(tzi.Bias + tzi.StandardBias)
+ * cast(d_time)(60 * ticksPerSecond);
+ case TIME_ZONE_ID_DAYLIGHT:
+ // falthrough
+ //t = -(tzi.Bias + tzi.DaylightBias) * cast(d_time)(60 * ticksPerSecond);
+ //break;
+ case TIME_ZONE_ID_UNKNOWN:
+ return -(tzi.Bias) * cast(d_time)(60 * ticksPerSecond);
+ default:
+ return 0;
+ }
+ }
+
+ /*
+ * Get daylight savings time adjust for time dt.
+ */
+
+ int daylightSavingTA(d_time dt)
+ {
+ TIME_ZONE_INFORMATION tzi = void;
+ d_time ts;
+ d_time td;
+
+ /* http://msdn.microsoft.com/library/en-us/sysinfo/base/gettimezoneinformation.asp
+ */
+ auto r = GetTimeZoneInformation(&tzi);
+ auto t = 0;
+ switch (r)
+ {
+ case TIME_ZONE_ID_STANDARD:
+ case TIME_ZONE_ID_DAYLIGHT:
+ if (tzi.StandardDate.wMonth == 0 ||
+ tzi.DaylightDate.wMonth == 0)
+ break;
+
+ ts = SYSTEMTIME2d_time(&tzi.StandardDate, dt);
+ td = SYSTEMTIME2d_time(&tzi.DaylightDate, dt);
+
+ if (td <= dt && dt < ts)
+ {
+ t = -tzi.DaylightBias * (60 * ticksPerSecond);
+ //printf("DST is in effect, %d\n", t);
+ }
+ else
+ {
+ //printf("no DST\n");
+ }
+ break;
+
+ case TIME_ZONE_ID_UNKNOWN:
+ // Daylight savings time not used in this time zone
+ break;
+
+ default:
+ assert(0);
+ }
+ return t;
+ }
+}
+
+version (Posix)
+{
+ private import core.sys.posix.time;
+ private import core.sys.posix.sys.time;
+
+ /******
+ * Get current UTC time.
+ */
+ d_time getUTCtime()
+ { timeval tv;
+
+ //printf("getUTCtime()\n");
+ if (gettimeofday(&tv, null))
+ { // Some error happened - try time() instead
+ return time(null) * ticksPerSecond;
+ }
+
+ return tv.tv_sec * cast(d_time)ticksPerSecond +
+ (tv.tv_usec / (1000000 / cast(d_time)ticksPerSecond));
+ }
+
+ d_time getLocalTZA()
+ {
+ time_t t;
+
+ time(&t);
+ version (OSX)
+ {
+ tm result;
+ localtime_r(&t, &result);
+ return result.tm_gmtoff * ticksPerSecond;
+ }
+ else version (FreeBSD)
+ {
+ tm result;
+ localtime_r(&t, &result);
+ return result.tm_gmtoff * ticksPerSecond;
+ }
+ else
+ {
+ localtime(&t); // this will set timezone
+ return -(timezone * ticksPerSecond);
+ }
+ }
+
+ /*
+ * Get daylight savings time adjust for time dt.
+ */
+
+ int daylightSavingTA(d_time dt)
+ {
+ tm *tmp;
+ time_t t;
+ int dst = 0;
+
+ if (dt != d_time_nan)
+ {
+ d_time seconds = dt / ticksPerSecond;
+ t = cast(time_t) seconds;
+ if (t == seconds) // if in range
+ {
+ tmp = localtime(&t);
+ if (tmp.tm_isdst > 0)
+ dst = ticksPerHour; // BUG: Assume daylight savings time is plus one hour.
+ }
+ else // out of range for system time, use our own calculation
+ {
+ /* BUG: this works for the US, but not other timezones.
+ */
+
+ dt -= localTZA;
+
+ int year = yearFromTime(dt);
+
+ /* Compute time given year, month 1..12,
+ * week in month, weekday, hour
+ */
+ d_time dstt(int year, int month, int week, int weekday, int hour)
+ {
+ auto mday = dateFromNthWeekdayOfMonth(year, month, weekday, week);
+ return timeClip(makeDate(
+ makeDay(year, month - 1, mday),
+ makeTime(hour, 0, 0, 0)));
+ }
+
+ d_time start;
+ d_time end;
+ if (year < 2007)
+ { // Daylight savings time goes from 2 AM the first Sunday
+ // in April through 2 AM the last Sunday in October
+ start = dstt(year, 4, 1, 0, 2);
+ end = dstt(year, 10, 5, 0, 2);
+ }
+ else
+ {
+ // the second Sunday of March to
+ // the first Sunday in November
+ start = dstt(year, 3, 2, 0, 2);
+ end = dstt(year, 11, 1, 0, 2);
+ }
+
+ if (start <= dt && dt < end)
+ dst = ticksPerHour;
+ //writefln("start = %s, dt = %s, end = %s, dst = %s", start, dt, end, dst);
+ }
+ }
+ return dst;
+ }
+
+}
+
+
+/+ DOS File Time +/
+
+/***
+ * Type representing the DOS file date/time format.
+ */
+alias uint DosFileTime;
+
+/************************************
+ * Convert from DOS file date/time to d_time.
+ */
+
+d_time toDtime(DosFileTime time)
+{
+ uint dt = cast(uint)time;
+
+ if (dt == 0)
+ return d_time_nan;
+
+ int year = ((dt >> 25) & 0x7F) + 1980;
+ int month = ((dt >> 21) & 0x0F) - 1; // 0..12
+ int dayofmonth = ((dt >> 16) & 0x1F); // 0..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)
+
+ d_time t;
+
+ t = undead.date.makeDate(undead.date.makeDay(year, month, dayofmonth),
+ undead.date.makeTime(hour, minute, second, 0));
+
+ assert(yearFromTime(t) == year);
+ assert(monthFromTime(t) == month);
+ assert(dateFromTime(t) == dayofmonth);
+ assert(hourFromTime(t) == hour);
+ assert(minFromTime(t) == minute);
+ assert(secFromTime(t) == second);
+
+ t -= localTZA + daylightSavingTA(t);
+
+ return t;
+}
+
+/****************************************
+ * Convert from d_time to DOS file date/time.
+ */
+
+DosFileTime toDosFileTime(d_time t)
+{ uint dt;
+
+ if (t == d_time_nan)
+ return cast(DosFileTime)0;
+
+ t += localTZA + daylightSavingTA(t);
+
+ uint year = yearFromTime(t);
+ uint month = monthFromTime(t);
+ uint dayofmonth = dateFromTime(t);
+ uint hour = hourFromTime(t);
+ uint minute = minFromTime(t);
+ uint second = secFromTime(t);
+
+ dt = (year - 1980) << 25;
+ dt |= ((month + 1) & 0x0F) << 21;
+ dt |= (dayofmonth & 0x1F) << 16;
+ dt |= (hour & 0x1F) << 11;
+ dt |= (minute & 0x3F) << 5;
+ dt |= (second >> 1) & 0x1F;
+
+ return cast(DosFileTime)dt;
+}
+
+/**
+Benchmarks code for speed assessment and comparison.
+
+Params:
+
+fun = aliases of callable objects (e.g. function names). Each should
+take no arguments.
+
+times = The number of times each function is to be executed.
+
+result = The optional store for the return value. If $(D null) is
+passed in, new store is allocated appropriately.
+
+Returns:
+
+An array of $(D n) $(D uint)s. Element at slot $(D i) contains the
+number of milliseconds spent in calling the $(D i)th function $(D
+times) times.
+
+Example:
+----
+int a;
+void f0() { }
+void f1() { auto b = a; }
+void f2() { auto b = to!(string)(a); }
+auto r = benchmark!(f0, f1, f2)(10_000_000);
+----
+ */
+ulong[] benchmark(fun...)(uint times, ulong[] result = null)
+{
+ result.length = fun.length;
+ result.length = 0;
+ foreach (i, Unused; fun)
+ {
+ immutable t = getUTCtime();
+ foreach (j; 0 .. times)
+ {
+ fun[i]();
+ }
+ immutable delta = getUTCtime() - t;
+ result ~= cast(uint)delta;
+ }
+ foreach (ref e; result)
+ {
+ e *= 1000;
+ e /= ticksPerSecond;
+ }
+ return result;
+}
+
+unittest
+{
+ int a;
+ void f0() { }
+ //void f1() { auto b = to!(string)(a); }
+ void f2() { auto b = (a); }
+ auto r = benchmark!(f0, f2)(100);
+ //writeln(r);
+}
diff --git a/src/undead/datebase.d b/src/undead/datebase.d
new file mode 100644
index 0000000..ec74cc7
--- /dev/null
+++ b/src/undead/datebase.d
@@ -0,0 +1,25 @@
+// Written in the D programming language.
+
+/**
+ * The only purpose of this module is to do the static construction for
+ * std.date, to eliminate cyclic construction errors.
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
+ * Authors: $(WEB digitalmars.com, Walter Bright)
+ * Source: $(PHOBOSSRC std/_datebase.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 undead.datebase;
+
+extern(C) void std_date_static_this();
+
+shared static this()
+{
+ std_date_static_this();
+}
diff --git a/src/undead/dateparse.d b/src/undead/dateparse.d
new file mode 100644
index 0000000..50033b3
--- /dev/null
+++ b/src/undead/dateparse.d
@@ -0,0 +1,784 @@
+// Written in the D programming language.
+
+/**
+ * $(RED Deprecated. It will be removed in February 2012.
+ * Please use std.datetime instead.)
+ *
+ * dateparse module.
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2009.
+ * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
+ * Authors: $(WEB digitalmars.com, Walter Bright)
+ * Source: $(PHOBOSSRC std/_dateparse.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 undead.dateparse;
+
+private
+{
+ import std.algorithm, std.string;
+ import core.stdc.stdlib;
+ import undead.date;
+}
+
+//deprecated:
+
+//debug=dateparse;
+
+class DateParseError : Error
+{
+ this(string s)
+ {
+ super("Invalid date string: " ~ s);
+ }
+}
+
+struct DateParse
+{
+ void parse(string s, out Date date)
+ {
+ this = DateParse.init;
+
+ //version (Win32)
+ buffer = (cast(char *)alloca(s.length))[0 .. s.length];
+ //else
+ //buffer = new char[s.length];
+
+ debug(dateparse) printf("DateParse.parse('%.*s')\n", s);
+ if (!parseString(s))
+ {
+ goto Lerror;
+ }
+
+ /+
+ if (year == year.init)
+ year = 0;
+ else
+ +/
+ debug(dateparse)
+ printf("year = %d, month = %d, day = %d\n%02d:%02d:%02d.%03d\nweekday = %d, tzcorrection = %d\n",
+ year, month, day,
+ hours, minutes, seconds, ms,
+ weekday, tzcorrection);
+ if (
+ year == year.init ||
+ (month < 1 || month > 12) ||
+ (day < 1 || day > 31) ||
+ (hours < 0 || hours > 23) ||
+ (minutes < 0 || minutes > 59) ||
+ (seconds < 0 || seconds > 59) ||
+ (tzcorrection != int.min &&
+ ((tzcorrection < -2300 || tzcorrection > 2300) ||
+ (tzcorrection % 10)))
+ )
+ {
+ Lerror:
+ throw new DateParseError(s);
+ }
+
+ if (ampm)
+ { if (hours > 12)
+ goto Lerror;
+ if (hours < 12)
+ {
+ if (ampm == 2) // if P.M.
+ hours += 12;
+ }
+ else if (ampm == 1) // if 12am
+ {
+ hours = 0; // which is midnight
+ }
+ }
+
+// if (tzcorrection != tzcorrection.init)
+// tzcorrection /= 100;
+
+ if (year >= 0 && year <= 99)
+ year += 1900;
+
+ date.year = year;
+ date.month = month;
+ date.day = day;
+ date.hour = hours;
+ date.minute = minutes;
+ date.second = seconds;
+ date.ms = ms;
+ date.weekday = weekday;
+ date.tzcorrection = tzcorrection;
+ }
+
+
+private:
+ int year = int.min; // our "nan" Date value
+ int month; // 1..12
+ int day; // 1..31
+ int hours; // 0..23
+ int minutes; // 0..59
+ int seconds; // 0..59
+ int ms; // 0..999
+ int weekday; // 1..7
+ int ampm; // 0: not specified
+ // 1: AM
+ // 2: PM
+ int tzcorrection = int.min; // -1200..1200 correction in hours
+
+ string s;
+ int si;
+ int number;
+ char[] buffer;
+
+ enum DP : byte
+ {
+ err,
+ weekday,
+ month,
+ number,
+ end,
+ colon,
+ minus,
+ slash,
+ ampm,
+ plus,
+ tz,
+ dst,
+ dsttz,
+ }
+
+ DP nextToken()
+ { int nest;
+ uint c;
+ int bi;
+ DP result = DP.err;
+
+ //printf("DateParse::nextToken()\n");
+ for (;;)
+ {
+ assert(si <= s.length);
+ if (si == s.length)
+ { result = DP.end;
+ goto Lret;
+ }
+ //printf("\ts[%d] = '%c'\n", si, s[si]);
+ switch (s[si])
+ {
+ case ':': result = DP.colon; goto ret_inc;
+ case '+': result = DP.plus; goto ret_inc;
+ case '-': result = DP.minus; goto ret_inc;
+ case '/': result = DP.slash; goto ret_inc;
+ case '.':
+ version(DATE_DOT_DELIM)
+ {
+ result = DP.slash;
+ goto ret_inc;
+ }
+ else
+ {
+ si++;
+ break;
+ }
+
+ ret_inc:
+ si++;
+ goto Lret;
+
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ case ',':
+ si++;
+ break;
+
+ case '(': // comment
+ nest = 1;
+ for (;;)
+ {
+ si++;
+ if (si == s.length)
+ goto Lret; // error
+ switch (s[si])
+ {
+ case '(':
+ nest++;
+ break;
+
+ case ')':
+ if (--nest == 0)
+ goto Lendofcomment;
+ break;
+
+ default:
+ break;
+ }
+ }
+ Lendofcomment:
+ si++;
+ break;
+
+ default:
+ number = 0;
+ for (;;)
+ {
+ if (si == s.length)
+ // c cannot be undefined here
+ break;
+ c = s[si];
+ if (!(c >= '0' && c <= '9'))
+ break;
+ result = DP.number;
+ number = number * 10 + (c - '0');
+ si++;
+ }
+ if (result == DP.number)
+ goto Lret;
+
+ bi = 0;
+ bufloop:
+ while (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')
+ {
+ if (c < 'a') // if upper case
+ c += cast(uint)'a' - cast(uint)'A'; // to lower case
+ buffer[bi] = cast(char)c;
+ bi++;
+ do
+ {
+ si++;
+ if (si == s.length)
+ break bufloop;
+ c = s[si];
+ } while (c == '.'); // ignore embedded '.'s
+ }
+ result = classify(buffer[0 .. bi].idup);
+ goto Lret;
+ }
+ }
+ Lret:
+ //printf("-DateParse::nextToken()\n");
+ return result;
+ }
+
+ DP classify(string buf)
+ {
+ struct DateID
+ {
+ string name;
+ DP tok;
+ short value;
+ }
+
+ static immutable DateID[] dateidtab =
+ [
+ { "january", DP.month, 1},
+ { "february", DP.month, 2},
+ { "march", DP.month, 3},
+ { "april", DP.month, 4},
+ { "may", DP.month, 5},
+ { "june", DP.month, 6},
+ { "july", DP.month, 7},
+ { "august", DP.month, 8},
+ { "september", DP.month, 9},
+ { "october", DP.month, 10},
+ { "november", DP.month, 11},
+ { "december", DP.month, 12},
+ { "jan", DP.month, 1},
+ { "feb", DP.month, 2},
+ { "mar", DP.month, 3},
+ { "apr", DP.month, 4},
+ { "jun", DP.month, 6},
+ { "jul", DP.month, 7},
+ { "aug", DP.month, 8},
+ { "sep", DP.month, 9},
+ { "sept", DP.month, 9},
+ { "oct", DP.month, 10},
+ { "nov", DP.month, 11},
+ { "dec", DP.month, 12},
+
+ { "sunday", DP.weekday, 1},
+ { "monday", DP.weekday, 2},
+ { "tuesday", DP.weekday, 3},
+ { "tues", DP.weekday, 3},
+ { "wednesday", DP.weekday, 4},
+ { "wednes", DP.weekday, 4},
+ { "thursday", DP.weekday, 5},
+ { "thur", DP.weekday, 5},
+ { "thurs", DP.weekday, 5},
+ { "friday", DP.weekday, 6},
+ { "saturday", DP.weekday, 7},
+
+ { "sun", DP.weekday, 1},
+ { "mon", DP.weekday, 2},
+ { "tue", DP.weekday, 3},
+ { "wed", DP.weekday, 4},
+ { "thu", DP.weekday, 5},
+ { "fri", DP.weekday, 6},
+ { "sat", DP.weekday, 7},
+
+ { "am", DP.ampm, 1},
+ { "pm", DP.ampm, 2},
+
+ { "gmt", DP.tz, +000},
+ { "ut", DP.tz, +000},
+ { "utc", DP.tz, +000},
+ { "wet", DP.tz, +000},
+ { "z", DP.tz, +000},
+ { "wat", DP.tz, +100},
+ { "a", DP.tz, +100},
+ { "at", DP.tz, +200},
+ { "b", DP.tz, +200},
+ { "c", DP.tz, +300},
+ { "ast", DP.tz, +400},
+ { "d", DP.tz, +400},
+ { "est", DP.tz, +500},
+ { "e", DP.tz, +500},
+ { "cst", DP.tz, +600},
+ { "f", DP.tz, +600},
+ { "mst", DP.tz, +700},
+ { "g", DP.tz, +700},
+ { "pst", DP.tz, +800},
+ { "h", DP.tz, +800},
+ { "yst", DP.tz, +900},
+ { "i", DP.tz, +900},
+ { "ahst", DP.tz, +1000},
+ { "cat", DP.tz, +1000},
+ { "hst", DP.tz, +1000},
+ { "k", DP.tz, +1000},
+ { "nt", DP.tz, +1100},
+ { "l", DP.tz, +1100},
+ { "idlw", DP.tz, +1200},
+ { "m", DP.tz, +1200},
+
+ { "cet", DP.tz, -100},
+ { "fwt", DP.tz, -100},
+ { "met", DP.tz, -100},
+ { "mewt", DP.tz, -100},
+ { "swt", DP.tz, -100},
+ { "n", DP.tz, -100},
+ { "eet", DP.tz, -200},
+ { "o", DP.tz, -200},
+ { "bt", DP.tz, -300},
+ { "p", DP.tz, -300},
+ { "zp4", DP.tz, -400},
+ { "q", DP.tz, -400},
+ { "zp5", DP.tz, -500},
+ { "r", DP.tz, -500},
+ { "zp6", DP.tz, -600},
+ { "s", DP.tz, -600},
+ { "wast", DP.tz, -700},
+ { "t", DP.tz, -700},
+ { "cct", DP.tz, -800},
+ { "u", DP.tz, -800},
+ { "jst", DP.tz, -900},
+ { "v", DP.tz, -900},
+ { "east", DP.tz, -1000},
+ { "gst", DP.tz, -1000},
+ { "w", DP.tz, -1000},
+ { "x", DP.tz, -1100},
+ { "idle", DP.tz, -1200},
+ { "nzst", DP.tz, -1200},
+ { "nzt", DP.tz, -1200},
+ { "y", DP.tz, -1200},
+
+ { "bst", DP.dsttz, 000},
+ { "adt", DP.dsttz, +400},
+ { "edt", DP.dsttz, +500},
+ { "cdt", DP.dsttz, +600},
+ { "mdt", DP.dsttz, +700},
+ { "pdt", DP.dsttz, +800},
+ { "ydt", DP.dsttz, +900},
+ { "hdt", DP.dsttz, +1000},
+ { "mest", DP.dsttz, -100},
+ { "mesz", DP.dsttz, -100},
+ { "sst", DP.dsttz, -100},
+ { "fst", DP.dsttz, -100},
+ { "wadt", DP.dsttz, -700},
+ { "eadt", DP.dsttz, -1000},
+ { "nzdt", DP.dsttz, -1200},
+
+ { "dst", DP.dst, 0},
+ ];
+
+ //message(DTEXT("DateParse::classify('%s')\n"), buf);
+
+ // Do a linear search. Yes, it would be faster with a binary
+ // one.
+ for (uint i = 0; i < dateidtab.length; i++)
+ {
+ if (cmp(dateidtab[i].name, buf) == 0)
+ {
+ number = dateidtab[i].value;
+ return dateidtab[i].tok;
+ }
+ }
+ return DP.err;
+ }
+
+ int parseString(string s)
+ {
+ int n1;
+ int dp;
+ int sisave;
+ int result;
+
+ //message(DTEXT("DateParse::parseString('%ls')\n"), s);
+ this.s = s;
+ si = 0;
+ dp = nextToken();
+ for (;;)
+ {
+ //message(DTEXT("\tdp = %d\n"), dp);
+ switch (dp)
+ {
+ case DP.end:
+ result = 1;
+ Lret:
+ return result;
+
+ case DP.err:
+ case_error:
+ //message(DTEXT("\terror\n"));
+ default:
+ result = 0;
+ goto Lret;
+
+ case DP.minus:
+ break; // ignore spurious '-'
+
+ case DP.weekday:
+ weekday = number;
+ break;
+
+ case DP.month: // month day, [year]
+ month = number;
+ dp = nextToken();
+ if (dp == DP.number)
+ {
+ day = number;
+ sisave = si;
+ dp = nextToken();
+ if (dp == DP.number)
+ {
+ n1 = number;
+ dp = nextToken();
+ if (dp == DP.colon)
+ { // back up, not a year
+ si = sisave;
+ }
+ else
+ { year = n1;
+ continue;
+ }
+ break;
+ }
+ }
+ continue;
+
+ case DP.number:
+ n1 = number;
+ dp = nextToken();
+ switch (dp)
+ {
+ case DP.end:
+ year = n1;
+ break;
+
+ case DP.minus:
+ case DP.slash: // n1/ ? ? ?
+ dp = parseCalendarDate(n1);
+ if (dp == DP.err)
+ goto case_error;
+ break;
+
+ case DP.colon: // hh:mm [:ss] [am | pm]
+ dp = parseTimeOfDay(n1);
+ if (dp == DP.err)
+ goto case_error;
+ break;
+
+ case DP.ampm:
+ hours = n1;
+ minutes = 0;
+ seconds = 0;
+ ampm = number;
+ break;
+
+ case DP.month:
+ day = n1;
+ month = number;
+ dp = nextToken();
+ if (dp == DP.number)
+ { // day month year
+ year = number;
+ dp = nextToken();
+ }
+ break;
+
+ default:
+ year = n1;
+ break;
+ }
+ continue;
+ }
+ dp = nextToken();
+ }
+ // @@@ bug in the compiler: this is never reachable
+ assert(0);
+ }
+
+ int parseCalendarDate(int n1)
+ {
+ int n2;
+ int n3;
+ int dp;
+
+ debug(dateparse) printf("DateParse.parseCalendarDate(%d)\n", n1);
+ dp = nextToken();
+ if (dp == DP.month) // day/month
+ {
+ day = n1;
+ month = number;
+ dp = nextToken();
+ if (dp == DP.number)
+ { // day/month year
+ year = number;
+ dp = nextToken();
+ }
+ else if (dp == DP.minus || dp == DP.slash)
+ { // day/month/year
+ dp = nextToken();
+ if (dp != DP.number)
+ goto case_error;
+ year = number;
+ dp = nextToken();
+ }
+ return dp;
+ }
+ if (dp != DP.number)
+ goto case_error;
+ n2 = number;
+ //message(DTEXT("\tn2 = %d\n"), n2);
+ dp = nextToken();
+ if (dp == DP.minus || dp == DP.slash)
+ {
+ dp = nextToken();
+ if (dp != DP.number)
+ goto case_error;
+ n3 = number;
+ //message(DTEXT("\tn3 = %d\n"), n3);
+ dp = nextToken();
+
+ // case1: year/month/day
+ // case2: month/day/year
+ int case1, case2;
+
+ case1 = (n1 > 12 ||
+ (n2 >= 1 && n2 <= 12) &&
+ (n3 >= 1 && n3 <= 31));
+ case2 = ((n1 >= 1 && n1 <= 12) &&
+ (n2 >= 1 && n2 <= 31) ||
+ n3 > 31);
+ if (case1 == case2)
+ goto case_error;
+ if (case1)
+ {
+ year = n1;
+ month = n2;
+ day = n3;
+ }
+ else
+ {
+ month = n1;
+ day = n2;
+ year = n3;
+ }
+ }
+ else
+ { // must be month/day
+ month = n1;
+ day = n2;
+ }
+ return dp;
+
+ case_error:
+ return DP.err;
+ }
+
+ int parseTimeOfDay(int n1)
+ {
+ int dp;
+ int sign;
+
+ // 12am is midnight
+ // 12pm is noon
+
+ //message(DTEXT("DateParse::parseTimeOfDay(%d)\n"), n1);
+ hours = n1;
+ dp = nextToken();
+ if (dp != DP.number)
+ goto case_error;
+ minutes = number;
+ dp = nextToken();
+ if (dp == DP.colon)
+ {
+ dp = nextToken();
+ if (dp != DP.number)
+ goto case_error;
+ seconds = number;
+ dp = nextToken();
+ }
+ else
+ seconds = 0;
+
+ if (dp == DP.ampm)
+ {
+ ampm = number;
+ dp = nextToken();
+ }
+ else if (dp == DP.plus || dp == DP.minus)
+ {
+ Loffset:
+ sign = (dp == DP.minus) ? -1 : 1;
+ dp = nextToken();
+ if (dp != DP.number)
+ goto case_error;
+ tzcorrection = -sign * number;
+ dp = nextToken();
+ }
+ else if (dp == DP.tz)
+ {
+ tzcorrection = number;
+ dp = nextToken();
+ if (number == 0 && (dp == DP.plus || dp == DP.minus))
+ goto Loffset;
+ if (dp == DP.dst)
+ { tzcorrection += 100;
+ dp = nextToken();
+ }
+ }
+ else if (dp == DP.dsttz)
+ {
+ tzcorrection = number;
+ dp = nextToken();
+ }
+
+ return dp;
+
+ case_error:
+ return DP.err;
+ }
+
+}
+
+unittest
+{
+ DateParse dp;
+ Date d;
+
+ dp.parse("March 10, 1959 12:00 -800", d);
+ assert(d.year == 1959);
+ assert(d.month == 3);
+ assert(d.day == 10);
+ assert(d.hour == 12);
+ assert(d.minute == 0);
+ assert(d.second == 0);
+ assert(d.ms == 0);
+ assert(d.weekday == 0);
+ assert(d.tzcorrection == 800);
+
+ dp.parse("Tue Apr 02 02:04:57 GMT-0800 1996", d);
+ assert(d.year == 1996);
+ assert(d.month == 4);
+ assert(d.day == 2);
+ assert(d.hour == 2);
+ assert(d.minute == 4);
+ assert(d.second == 57);
+ assert(d.ms == 0);
+ assert(d.weekday == 3);
+ assert(d.tzcorrection == 800);
+
+ dp.parse("March 14, -1980 21:14:50", d);
+ assert(d.year == 1980);
+ assert(d.month == 3);
+ assert(d.day == 14);
+ assert(d.hour == 21);
+ assert(d.minute == 14);
+ assert(d.second == 50);
+ assert(d.ms == 0);
+ assert(d.weekday == 0);
+ assert(d.tzcorrection == int.min);
+
+ dp.parse("Tue Apr 02 02:04:57 1996", d);
+ assert(d.year == 1996);
+ assert(d.month == 4);
+ assert(d.day == 2);
+ assert(d.hour == 2);
+ assert(d.minute == 4);
+ assert(d.second == 57);
+ assert(d.ms == 0);
+ assert(d.weekday == 3);
+ assert(d.tzcorrection == int.min);
+
+ dp.parse("Tue, 02 Apr 1996 02:04:57 G.M.T.", d);
+ assert(d.year == 1996);
+ assert(d.month == 4);
+ assert(d.day == 2);
+ assert(d.hour == 2);
+ assert(d.minute == 4);
+ assert(d.second == 57);
+ assert(d.ms == 0);
+ assert(d.weekday == 3);
+ assert(d.tzcorrection == 0);
+
+ dp.parse("December 31, 3000", d);
+ assert(d.year == 3000);
+ assert(d.month == 12);
+ assert(d.day == 31);
+ assert(d.hour == 0);
+ assert(d.minute == 0);
+ assert(d.second == 0);
+ assert(d.ms == 0);
+ assert(d.weekday == 0);
+ assert(d.tzcorrection == int.min);
+
+ dp.parse("Wed, 31 Dec 1969 16:00:00 GMT", d);
+ assert(d.year == 1969);
+ assert(d.month == 12);
+ assert(d.day == 31);
+ assert(d.hour == 16);
+ assert(d.minute == 0);
+ assert(d.second == 0);
+ assert(d.ms == 0);
+ assert(d.weekday == 4);
+ assert(d.tzcorrection == 0);
+
+ dp.parse("1/1/1999 12:30 AM", d);
+ assert(d.year == 1999);
+ assert(d.month == 1);
+ assert(d.day == 1);
+ assert(d.hour == 0);
+ assert(d.minute == 30);
+ assert(d.second == 0);
+ assert(d.ms == 0);
+ assert(d.weekday == 0);
+ assert(d.tzcorrection == int.min);
+
+ dp.parse("Tue, 20 May 2003 15:38:58 +0530", d);
+ assert(d.year == 2003);
+ assert(d.month == 5);
+ assert(d.day == 20);
+ assert(d.hour == 15);
+ assert(d.minute == 38);
+ assert(d.second == 58);
+ assert(d.ms == 0);
+ assert(d.weekday == 3);
+ assert(d.tzcorrection == -530);
+
+ debug(dateparse) printf("year = %d, month = %d, day = %d\n%02d:%02d:%02d.%03d\nweekday = %d, tzcorrection = %d\n",
+ d.year, d.month, d.day,
+ d.hour, d.minute, d.second, d.ms,
+ d.weekday, d.tzcorrection);
+}
diff --git a/src/undead/doformat.d b/src/undead/doformat.d
new file mode 100644
index 0000000..4fc0daf
--- /dev/null
+++ b/src/undead/doformat.d
@@ -0,0 +1,1620 @@
+// Written in the D programming language.
+
+/**
+ Copyright: Copyright Digital Mars 2000-2013.
+
+ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+ Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
+ Andrei Alexandrescu), and Kenji Hara
+
+ Source: $(PHOBOSSRC std/_format.d)
+ */
+module undead.doformat;
+
+//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;
+import std.format;
+
+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);
+ }
+}
+
+
+// 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),
+ ±, followed by at least a two digit exponent: $(I d.dddddd)e$(I ±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 ±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 ±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");
+}
diff --git a/src/undead/internal/file.d b/src/undead/internal/file.d
new file mode 100644
index 0000000..f756674
--- /dev/null
+++ b/src/undead/internal/file.d
@@ -0,0 +1,25 @@
+// Written in the D programming language
+
+module undead.internal.file;
+
+// Copied from std.file. undead doesn't have access to it, but some modules
+// in undead used std.file.deleteme when they were in Phobos, so this gives
+// them access to a version of it.
+public @property string deleteme() @safe
+{
+ import std.conv : to;
+ import std.file : tempDir;
+ import std.path : buildPath;
+ 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;
+}
+
diff --git a/src/undead/metastrings.d b/src/undead/metastrings.d
new file mode 100644
index 0000000..44bf84f
--- /dev/null
+++ b/src/undead/metastrings.d
@@ -0,0 +1,218 @@
+// Written in the D programming language.
+
+/**
+Templates with which to do compile-time manipulation of strings.
+
+Macros:
+ WIKI = Phobos/StdMetastrings
+
+Copyright: Copyright Digital Mars 2007 - 2009.
+License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
+Authors: $(WEB digitalmars.com, Walter Bright),
+ Don Clugston
+Source: $(PHOBOSSRC std/_metastrings.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)
+ */
+module undead.metastrings;
+
+/**
+Formats constants into a string at compile time. Analogous to $(XREF
+string,format).
+
+Parameters:
+
+A = tuple of constants, which can be strings, characters, or integral
+ values.
+
+Formats:
+ * The formats supported are %s for strings, and %%
+ * for the % character.
+Example:
+---
+import std.metastrings;
+import std.stdio;
+
+void main()
+{
+ string s = Format!("Arg %s = %s", "foo", 27);
+ writefln(s); // "Arg foo = 27"
+}
+ * ---
+ */
+
+template Format(A...)
+{
+ static if (A.length == 0)
+ enum Format = "";
+ else static if (is(typeof(A[0]) : const(char)[]))
+ enum Format = FormatString!(A[0], A[1..$]);
+ else
+ enum Format = toStringNow!(A[0]) ~ Format!(A[1..$]);
+}
+
+template FormatString(const(char)[] F, A...)
+{
+ static if (F.length == 0)
+ enum FormatString = Format!(A);
+ else static if (F.length == 1)
+ enum FormatString = F[0] ~ Format!(A);
+ else static if (F[0..2] == "%s")
+ enum FormatString
+ = toStringNow!(A[0]) ~ FormatString!(F[2..$],A[1..$]);
+ else static if (F[0..2] == "%%")
+ enum FormatString = "%" ~ FormatString!(F[2..$],A);
+ else
+ {
+ static assert(F[0] != '%', "unrecognized format %" ~ F[1]);
+ enum FormatString = F[0] ~ FormatString!(F[1..$],A);
+ }
+}
+
+unittest
+{
+ auto s = Format!("hel%slo", "world", -138, 'c', true);
+ assert(s == "helworldlo-138ctrue", "[" ~ s ~ "]");
+}
+
+/**
+ * Convert constant argument to a string.
+ */
+
+template toStringNow(ulong v)
+{
+ static if (v < 10)
+ enum toStringNow = "" ~ cast(char)(v + '0');
+ else
+ enum toStringNow = toStringNow!(v / 10) ~ toStringNow!(v % 10);
+}
+
+unittest
+{
+ static assert(toStringNow!(1uL << 62) == "4611686018427387904");
+}
+
+/// ditto
+template toStringNow(long v)
+{
+ static if (v < 0)
+ enum toStringNow = "-" ~ toStringNow!(cast(ulong) -v);
+ else
+ enum toStringNow = toStringNow!(cast(ulong) v);
+}
+
+unittest
+{
+ static assert(toStringNow!(0x100000000) == "4294967296");
+ static assert(toStringNow!(-138L) == "-138");
+}
+
+/// ditto
+template toStringNow(uint U)
+{
+ enum toStringNow = toStringNow!(cast(ulong)U);
+}
+
+/// ditto
+template toStringNow(int I)
+{
+ enum toStringNow = toStringNow!(cast(long)I);
+}
+
+/// ditto
+template toStringNow(bool B)
+{
+ enum toStringNow = B ? "true" : "false";
+}
+
+/// ditto
+template toStringNow(string S)
+{
+ enum toStringNow = S;
+}
+
+/// ditto
+template toStringNow(char C)
+{
+ enum toStringNow = "" ~ C;
+}
+
+
+/********
+ * Parse unsigned integer literal from the start of string s.
+ * returns:
+ * .value = the integer literal as a string,
+ * .rest = the string following the integer literal
+ * Otherwise:
+ * .value = null,
+ * .rest = s
+ */
+
+template parseUinteger(const(char)[] s)
+{
+ static if (s.length == 0)
+ {
+ enum value = "";
+ enum rest = "";
+ }
+ else static if (s[0] >= '0' && s[0] <= '9')
+ {
+ enum value = s[0] ~ parseUinteger!(s[1..$]).value;
+ enum rest = parseUinteger!(s[1..$]).rest;
+ }
+ else
+ {
+ enum value = "";
+ enum rest = s;
+ }
+}
+
+/********
+Parse integer literal optionally preceded by $(D '-') from the start
+of string $(D s).
+
+Returns:
+ .value = the integer literal as a string,
+ .rest = the string following the integer literal
+
+Otherwise:
+ .value = null,
+ .rest = s
+*/
+
+template parseInteger(const(char)[] s)
+{
+ static if (s.length == 0)
+ {
+ enum value = "";
+ enum rest = "";
+ }
+ else static if (s[0] >= '0' && s[0] <= '9')
+ {
+ enum value = s[0] ~ parseUinteger!(s[1..$]).value;
+ enum rest = parseUinteger!(s[1..$]).rest;
+ }
+ else static if (s.length >= 2 &&
+ s[0] == '-' && s[1] >= '0' && s[1] <= '9')
+ {
+ enum value = s[0..2] ~ parseUinteger!(s[2..$]).value;
+ enum rest = parseUinteger!(s[2..$]).rest;
+ }
+ else
+ {
+ enum value = "";
+ enum rest = s;
+ }
+}
+
+unittest
+{
+ assert(parseUinteger!("1234abc").value == "1234");
+ assert(parseUinteger!("1234abc").rest == "abc");
+ assert(parseInteger!("-1234abc").value == "-1234");
+ assert(parseInteger!("-1234abc").rest == "abc");
+}
diff --git a/src/undead/regexp.d b/src/undead/regexp.d
new file mode 100644
index 0000000..0caa5de
--- /dev/null
+++ b/src/undead/regexp.d
@@ -0,0 +1,3439 @@
+// Written in the D programming language.
+// Regular Expressions.
+
+/**
+ * $(RED Deprecated. It will be removed in February 2012.
+ * Please use $(LINK2 std_regex.html, std.regex) instead.)
+ *
+ * $(LINK2 http://www.digitalmars.com/ctg/regular.html, Regular
+ * expressions) are a powerful method of string pattern matching. The
+ * regular expression language used in this library is the same as
+ * that commonly used, however, some of the very advanced forms may
+ * behave slightly differently. The standard observed is the $(WEB
+ * www.ecma-international.org/publications/standards/Ecma-262.htm,
+ * ECMA standard) for regular expressions.
+ *
+ * undead.regexp is designed to work only with valid UTF strings as input.
+ * To validate untrusted input, use std.utf.validate().
+ *
+ * In the following guide, $(I pattern)[] refers to a
+ * $(LINK2 http://www.digitalmars.com/ctg/regular.html, regular expression).
+ * The $(I attributes)[] refers to
+ * a string controlling the interpretation
+ * of the regular expression.
+ * It consists of a sequence of one or more
+ * of the following characters:
+ *
+ * <table border=1 cellspacing=0 cellpadding=5>
+ * <caption>Attribute Characters</caption>
+ * $(TR $(TH Attribute) $(TH Action))
+ * <tr>
+ * $(TD $(B g))
+ * $(TD global; repeat over the whole input string)
+ * </tr>
+ * <tr>
+ * $(TD $(B i))
+ * $(TD case insensitive)
+ * </tr>
+ * <tr>
+ * $(TD $(B m))
+ * $(TD treat as multiple lines separated by newlines)
+ * </tr>
+ * </table>
+ *
+ * The $(I format)[] string has the formatting characters:
+ *
+ * <table border=1 cellspacing=0 cellpadding=5>
+ * <caption>Formatting Characters</caption>
+ * $(TR $(TH Format) $(TH Replaced With))
+ * $(TR
+ * $(TD $(B $$)) $(TD $)
+ * )
+ * $(TR
+ * $(TD $(B $&)) $(TD The matched substring.)
+ * )
+ * $(TR
+ * $(TD $(B $`)) $(TD The portion of string that precedes the matched substring.)
+ * )
+ * $(TR
+ * $(TD $(B $')) $(TD The portion of string that follows the matched substring.)
+ * )
+ * $(TR
+ * $(TD $(B $(DOLLAR))$(I n)) $(TD The $(I n)th capture, where $(I n)
+ * is a single digit 1-9
+ * and $$(I n) is not followed by a decimal digit.)
+ * )
+ * $(TR
+ * $(TD $(B $(DOLLAR))$(I nn)) $(TD The $(I nn)th capture, where $(I nn)
+ * is a two-digit decimal
+ * number 01-99.
+ * If $(I nn)th capture is undefined or more than the number
+ * of parenthesized subexpressions, use the empty
+ * string instead.)
+ * )
+ * </table>
+ *
+ * Any other $ are left as is.
+ *
+ * References:
+ * $(LINK2 http://en.wikipedia.org/wiki/Regular_expressions, Wikipedia)
+ * Macros:
+ * WIKI = StdRegexp
+ * DOLLAR = $
+ *
+ * Copyright: Copyright Digital Mars 2000 - 2011.
+ * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
+ * Authors: $(WEB digitalmars.com, Walter Bright)
+ * Source: $(PHOBOSSRC std/_regexp.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)
+ */
+
+/*
+ Escape sequences:
+
+ \nnn starts out a 1, 2 or 3 digit octal sequence,
+ where n is an octal digit. If nnn is larger than
+ 0377, then the 3rd digit is not part of the sequence
+ and is not consumed.
+ For maximal portability, use exactly 3 digits.
+
+ \xXX starts out a 1 or 2 digit hex sequence. X
+ is a hex character. If the first character after the \x
+ is not a hex character, the value of the sequence is 'x'
+ and the XX are not consumed.
+ For maximal portability, use exactly 2 digits.
+
+ \uUUUU is a unicode sequence. There are exactly
+ 4 hex characters after the \u, if any are not, then
+ the value of the sequence is 'u', and the UUUU are not
+ consumed.
+
+ Character classes:
+
+ [a-b], where a is greater than b, will produce
+ an error.
+
+ References:
+
+ http://www.unicode.org/unicode/reports/tr18/
+*/
+
+module undead.regexp;
+
+//pragma(msg, "Notice: As of Phobos 2.055, std.regexp has been deprecated. " ~
+// "It will be removed in February 2012. Please use std.regex instead.");
+
+//debug = regexp; // uncomment to turn on debugging printf's
+
+private
+{
+ import core.stdc.stdio;
+ import core.stdc.stdlib;
+ import core.stdc.string;
+ import std.array;
+ import std.stdio;
+ import std.string;
+ import std.ascii;
+ import std.outbuffer;
+ import std.bitmanip;
+ import std.utf;
+ import std.algorithm;
+ import std.array;
+ import std.traits;
+}
+
+//deprecated:
+
+/** Regular expression to extract an _email address.
+ * References:
+ * $(LINK2 http://www.regular-expressions.info/email.html, How to Find or Validate an Email Address)$(BR)
+ * $(LINK2 http://tools.ietf.org/html/rfc2822#section-3.4.1, RFC 2822 Internet Message Format)
+ */
+string email =
+ r"[a-zA-Z]([.]?([[a-zA-Z0-9_]-]+)*)?@([[a-zA-Z0-9_]\-_]+\.)+[a-zA-Z]{2,6}";
+
+/** Regular expression to extract a _url */
+string url = r"(([h|H][t|T]|[f|F])[t|T][p|P]([s|S]?)\:\/\/|~/|/)?([\w]+:\w+@)?(([a-zA-Z]{1}([\w\-]+\.)+([\w]{2,5}))(:[\d]{1,5})?)?((/?\w+/)+|/?)(\w+\.[\w]{3,4})?([,]\w+)*((\?\w+=\w+)?(&\w+=\w+)*([,]\w*)*)?";
+
+/************************************
+ * One of these gets thrown on compilation errors
+ */
+
+class RegExpException : Exception
+{
+ this(string msg)
+ {
+ super(msg);
+ }
+}
+
+struct regmatch_t
+{
+ sizediff_t rm_so; // index of start of match
+ sizediff_t rm_eo; // index past end of match
+}
+
+private alias char rchar; // so we can make a wchar version
+
+/******************************************************
+ * Search string for matches with regular expression
+ * pattern with attributes.
+ * Replace each match with string generated from format.
+ * Params:
+ * s = String to search.
+ * pattern = Regular expression pattern.
+ * format = Replacement string format.
+ * attributes = Regular expression attributes.
+ * Returns:
+ * the resulting string
+ * Example:
+ * Replace the letters 'a' with the letters 'ZZ'.
+ * ---
+ * s = "Strap a rocket engine on a chicken."
+ * sub(s, "a", "ZZ") // result: StrZZp a rocket engine on a chicken.
+ * sub(s, "a", "ZZ", "g") // result: StrZZp ZZ rocket engine on ZZ chicken.
+ * ---
+ * The replacement format can reference the matches using
+ * the $&, $$, $', $`, $0 .. $99 notation:
+ * ---
+ * sub(s, "[ar]", "[$&]", "g") // result: St[r][a]p [a] [r]ocket engine on [a] chi
+ * ---
+ */
+
+string sub(string s, string pattern, string format, string attributes = null)
+{
+ auto r = new RegExp(pattern, attributes);
+ auto result = r.replace(s, format);
+ delete r;
+ return result;
+}
+
+unittest
+{
+ debug(regexp) printf("regexp.sub.unittest\n");
+
+ string r = sub("hello", "ll", "ss");
+ assert(r == "hesso");
+}
+
+/*******************************************************
+ * Search string for matches with regular expression
+ * pattern with attributes.
+ * Pass each match to delegate dg.
+ * Replace each match with the return value from dg.
+ * Params:
+ * s = String to search.
+ * pattern = Regular expression pattern.
+ * dg = Delegate
+ * attributes = Regular expression attributes.
+ * Returns: the resulting string.
+ * Example:
+ * Capitalize the letters 'a' and 'r':
+ * ---
+ * s = "Strap a rocket engine on a chicken.";
+ * sub(s, "[ar]",
+ * delegate char[] (RegExp m)
+ * {
+ * return toUpper(m[0]);
+ * },
+ * "g"); // result: StRAp A Rocket engine on A chicken.
+ * ---
+ */
+
+string sub(string s, string pattern, string delegate(RegExp) dg, string attributes = null)
+{
+ auto r = new RegExp(pattern, attributes);
+
+ string result = s;
+ size_t lastindex = 0;
+ size_t offset = 0;
+
+ while (r.test(s, lastindex))
+ {
+ auto so = r.pmatch[0].rm_so;
+ auto eo = r.pmatch[0].rm_eo;
+
+ string replacement = dg(r);
+
+ // Optimize by using std.string.replace if possible - Dave Fladebo
+ string slice = result[offset + so .. offset + eo];
+ if (r.attributes & RegExp.REA.global && // global, so replace all
+ !(r.attributes & RegExp.REA.ignoreCase) && // not ignoring case
+ !(r.attributes & RegExp.REA.multiline) && // not multiline
+ pattern == slice) // simple pattern (exact match, no special characters)
+ {
+ debug(regexp)
+ printf("result: %.*s, pattern: %.*s, slice: %.*s, replacement: %.*s\n",
+ result.length, result.ptr,
+ pattern.length, pattern.ptr,
+ slice.length, slice.ptr,
+ replacement.length, replacement.ptr);
+ result = replace(result,slice,replacement);
+ break;
+ }
+
+ result = replaceSlice(result, result[offset + so .. offset + eo], replacement);
+
+ if (r.attributes & RegExp.REA.global)
+ {
+ offset += replacement.length - (eo - so);
+
+ if (lastindex == eo)
+ lastindex++; // always consume some source
+ else
+ lastindex = eo;
+ }
+ else
+ break;
+ }
+ delete r;
+
+ return result;
+}
+
+unittest
+{
+ debug(regexp) printf("regexp.sub.unittest\n");
+
+ string foo(RegExp r) { return "ss"; }
+
+ auto r = sub("hello", "ll", delegate string(RegExp r) { return "ss"; });
+ assert(r == "hesso");
+
+ r = sub("hello", "l", delegate string(RegExp r) { return "l"; }, "g");
+ assert(r == "hello");
+
+ auto s = sub("Strap a rocket engine on a chicken.",
+ "[ar]",
+ delegate string (RegExp m)
+ {
+ return std.string.toUpper(m[0]);
+ },
+ "g");
+ assert(s == "StRAp A Rocket engine on A chicken.");
+}
+
+
+/*************************************************
+ * Search $(D_PARAM s[]) for first match with $(D_PARAM pattern).
+ * Params:
+ * s = String to search.
+ * pattern = Regular expression pattern.
+ * Returns:
+ * index into s[] of match if found, -1 if no match.
+ * Example:
+ * ---
+ * auto s = "abcabcabab";
+ * find(s, RegExp("b")); // match, returns 1
+ * find(s, RegExp("f")); // no match, returns -1
+ * ---
+ */
+
+sizediff_t find(string s, RegExp pattern)
+{
+ return pattern.test(s)
+ ? pattern.pmatch[0].rm_so
+ : -1;
+}
+
+unittest
+{
+ debug(regexp) printf("regexp.find.unittest\n");
+
+ auto i = find("xabcy", RegExp("abc"));
+ assert(i == 1);
+ i = find("cba", RegExp("abc"));
+ assert(i == -1);
+}
+
+/**
+ Returns:
+
+ Same as $(D_PARAM find(s, RegExp(pattern, attributes))).
+
+ WARNING:
+
+ This function is scheduled for deprecation due to unnecessary
+ ambiguity with the homonym function in std.string. Instead of
+ $(D_PARAM undead.regexp.find(s, p, a)), you may want to use $(D_PARAM
+ find(s, RegExp(p, a))).
+*/
+
+sizediff_t
+find(string s, string pattern, string attributes = null)
+{
+ auto r = new RegExp(pattern, attributes);
+ scope(exit) delete r;
+ return r.test(s) ? r.pmatch[0].rm_so : -1;
+}
+
+unittest
+{
+ debug(regexp) printf("regexp.find.unittest\n");
+
+ auto i = find("xabcy", "abc");
+ assert(i == 1);
+ i = find("cba", "abc");
+ assert(i == -1);
+}
+
+/*************************************************
+ * Search $(D_PARAM s[]) for last match with $(D_PARAM pattern).
+ * Params:
+ * s = String to search.
+ * pattern = Regular expression pattern.
+ * Returns:
+ * index into s[] of match if found, -1 if no match.
+ * Example:
+ * ---
+ * auto s = "abcabcabab";
+ * rfind(s, RegExp("b")); // match, returns 9
+ * rfind(s, RegExp("f")); // no match, returns -1
+ * ---
+ */
+
+sizediff_t rfind(string s, RegExp pattern)
+{
+ sizediff_t i = -1, lastindex = 0;
+
+ while (pattern.test(s, lastindex))
+ {
+ auto eo = pattern.pmatch[0].rm_eo;
+ i = pattern.pmatch[0].rm_so;
+ if (lastindex == eo)
+ lastindex++; // always consume some source
+ else
+ lastindex = eo;
+ }
+ return i;
+}
+
+unittest
+{
+ sizediff_t i;
+
+ debug(regexp) printf("regexp.rfind.unittest\n");
+ i = rfind("abcdefcdef", RegExp("c"));
+ assert(i == 6);
+ i = rfind("abcdefcdef", RegExp("cd"));
+ assert(i == 6);
+ i = rfind("abcdefcdef", RegExp("x"));
+ assert(i == -1);
+ i = rfind("abcdefcdef", RegExp("xy"));
+ assert(i == -1);
+ i = rfind("abcdefcdef", RegExp(""));
+ assert(i == 10);
+}
+
+/*************************************************
+Returns:
+
+ Same as $(D_PARAM rfind(s, RegExp(pattern, attributes))).
+
+WARNING:
+
+This function is scheduled for deprecation due to unnecessary
+ambiguity with the homonym function in std.string. Instead of
+$(D_PARAM undead.regexp.rfind(s, p, a)), you may want to use $(D_PARAM
+rfind(s, RegExp(p, a))).
+*/
+
+sizediff_t
+rfind(string s, string pattern, string attributes = null)
+{
+ typeof(return) i = -1, lastindex = 0;
+
+ auto r = new RegExp(pattern, attributes);
+ while (r.test(s, lastindex))
+ {
+ auto eo = r.pmatch[0].rm_eo;
+ i = r.pmatch[0].rm_so;
+ if (lastindex == eo)
+ lastindex++; // always consume some source
+ else
+ lastindex = eo;
+ }
+ delete r;
+ return i;
+}
+
+unittest
+{
+ sizediff_t i;
+
+ debug(regexp) printf("regexp.rfind.unittest\n");
+ i = rfind("abcdefcdef", "c");
+ assert(i == 6);
+ i = rfind("abcdefcdef", "cd");
+ assert(i == 6);
+ i = rfind("abcdefcdef", "x");
+ assert(i == -1);
+ i = rfind("abcdefcdef", "xy");
+ assert(i == -1);
+ i = rfind("abcdefcdef", "");
+ assert(i == 10);
+}
+
+
+/********************************************
+ * Split s[] into an array of strings, using the regular
+ * expression $(D_PARAM pattern) as the separator.
+ * Params:
+ * s = String to search.
+ * pattern = Regular expression pattern.
+ * Returns:
+ * array of slices into s[]
+ * Example:
+ * ---
+ * foreach (s; split("abcabcabab", RegExp("C.", "i")))
+ * {
+ * writefln("s = '%s'", s);
+ * }
+ * // Prints:
+ * // s = 'ab'
+ * // s = 'b'
+ * // s = 'bab'
+ * ---
+ */
+
+string[] split(string s, RegExp pattern)
+{
+ return pattern.split(s);
+}
+
+unittest
+{
+ debug(regexp) printf("regexp.split.unittest()\n");
+ string[] result;
+
+ result = split("ab", RegExp("a*"));
+ assert(result.length == 2);
+ assert(result[0] == "");
+ assert(result[1] == "b");
+
+ foreach (i, s; split("abcabcabab", RegExp("C.", "i")))
+ {
+ //writefln("s[%d] = '%s'", i, s);
+ if (i == 0) assert(s == "ab");
+ else if (i == 1) assert(s == "b");
+ else if (i == 2) assert(s == "bab");
+ else assert(0);
+ }
+}
+
+/********************************************
+ Returns:
+ Same as $(D_PARAM split(s, RegExp(pattern, attributes))).
+
+WARNING:
+
+This function is scheduled for deprecation due to unnecessary
+ambiguity with the homonym function in std.string. Instead of
+$(D_PARAM undead.regexp.split(s, p, a)), you may want to use $(D_PARAM
+split(s, RegExp(p, a))).
+*/
+
+string[] split(string s, string pattern, string attributes = null)
+{
+ auto r = new RegExp(pattern, attributes);
+ auto result = r.split(s);
+ delete r;
+ return result;
+}
+
+unittest
+{
+ debug(regexp) printf("regexp.split.unittest()\n");
+ string[] result;
+
+ result = split("ab", "a*");
+ assert(result.length == 2);
+ assert(result[0] == "");
+ assert(result[1] == "b");
+
+ foreach (i, s; split("abcabcabab", "C.", "i"))
+ {
+ //writefln("s[%d] = '%s'", i, s.length, s.ptr);
+ if (i == 0) assert(s == "ab");
+ else if (i == 1) assert(s == "b");
+ else if (i == 2) assert(s == "bab");
+ else assert(0);
+ }
+}
+
+/****************************************************
+ * Search s[] for first match with pattern[] with attributes[].
+ * Params:
+ * s = String to search.
+ * pattern = Regular expression pattern.
+ * attributes = Regular expression attributes.
+ * Returns:
+ * corresponding RegExp if found, null if not.
+ * Example:
+ * ---
+ * import std.stdio;
+ * import undead.regexp;
+ *
+ * void main()
+ * {
+ * if (auto m = undead.regexp.search("abcdef", "c"))
+ * {
+ * writefln("%s[%s]%s", m.pre, m[0], m.post);
+ * }
+ * }
+ * // Prints:
+ * // ab[c]def
+ * ---
+ */
+
+RegExp search(string s, string pattern, string attributes = null)
+{
+ auto r = new RegExp(pattern, attributes);
+ if (!r.test(s))
+ { delete r;
+ assert(r is null);
+ }
+ return r;
+}
+
+unittest
+{
+ debug(regexp) printf("regexp.string.unittest()\n");
+
+ if (auto m = undead.regexp.search("abcdef", "c()"))
+ {
+ auto result = std.string.format("%s[%s]%s", m.pre, m[0], m.post);
+ assert(result == "ab[c]def");
+ assert(m[1] == null);
+ assert(m[2] == null);
+ }
+ else
+ assert(0);
+
+ if (auto n = undead.regexp.search("abcdef", "g"))
+ {
+ assert(0);
+ }
+}
+
+/* ********************************* RegExp ******************************** */
+
+/*****************************
+ * RegExp is a class to handle regular expressions.
+ *
+ * It is the core foundation for adding powerful string pattern matching
+ * capabilities to programs like grep, text editors, awk, sed, etc.
+ */
+class RegExp
+{
+ /*****
+ * Construct a RegExp object. Compile pattern
+ * with <i>attributes</i> into
+ * an internal form for fast execution.
+ * Params:
+ * pattern = regular expression
+ * attributes = _attributes
+ * Throws: RegExpException if there are any compilation errors.
+ * Example:
+ * Declare two variables and assign to them a RegExp object:
+ * ---
+ * auto r = new RegExp("pattern");
+ * auto s = new RegExp(r"p[1-5]\s*");
+ * ---
+ */
+ public this(string pattern, string attributes = null)
+ {
+ pmatch = (&gmatch)[0 .. 1];
+ compile(pattern, attributes);
+ }
+
+ /*****
+ * Generate instance of RegExp.
+ * Params:
+ * pattern = regular expression
+ * attributes = _attributes
+ * Throws: RegExpException if there are any compilation errors.
+ * Example:
+ * Declare two variables and assign to them a RegExp object:
+ * ---
+ * auto r = RegExp("pattern");
+ * auto s = RegExp(r"p[1-5]\s*");
+ * ---
+ */
+ public static RegExp opCall(string pattern, string attributes = null)
+ {
+ return new RegExp(pattern, attributes);
+ }
+
+ unittest
+ {
+ debug(regexp) printf("regexp.opCall.unittest()\n");
+ auto r1 = RegExp("hello", "m");
+ string msg;
+ try
+ {
+ auto r2 = RegExp("hello", "q");
+ assert(0);
+ }
+ catch (RegExpException ree)
+ {
+ msg = ree.toString();
+ //writefln("message: %s", ree);
+ }
+ assert(std.algorithm.countUntil(msg, "unrecognized attribute") >= 0);
+ }
+
+ /************************************
+ * Set up for start of foreach loop.
+ * Returns:
+ * search() returns instance of RegExp set up to _search string[].
+ * Example:
+ * ---
+ * import std.stdio;
+ * import undead.regexp;
+ *
+ * void main()
+ * {
+ * foreach(m; RegExp("ab").search("abcabcabab"))
+ * {
+ * writefln("%s[%s]%s", m.pre, m[0], m.post);
+ * }
+ * }
+ * // Prints:
+ * // [ab]cabcabab
+ * // abc[ab]cabab
+ * // abcabc[ab]ab
+ * // abcabcab[ab]
+ * ---
+ */
+
+ public RegExp search(string string)
+ {
+ input = string;
+ pmatch[0].rm_eo = 0;
+ return this;
+ }
+
+ /** ditto */
+ public int opApply(scope int delegate(ref RegExp) dg)
+ {
+ int result;
+ RegExp r = this;
+
+ while (test())
+ {
+ result = dg(r);
+ if (result)
+ break;
+ }
+
+ return result;
+ }
+
+ unittest
+ {
+ debug(regexp) printf("regexp.search.unittest()\n");
+
+ int i;
+ foreach(m; RegExp("ab").search("abcabcabab"))
+ {
+ auto s = std.string.format("%s[%s]%s", m.pre, m[0], m.post);
+ if (i == 0) assert(s == "[ab]cabcabab");
+ else if (i == 1) assert(s == "abc[ab]cabab");
+ else if (i == 2) assert(s == "abcabc[ab]ab");
+ else if (i == 3) assert(s == "abcabcab[ab]");
+ else assert(0);
+ i++;
+ }
+ }
+
+ /******************
+ * Retrieve match n.
+ *
+ * n==0 means the matched substring, n>0 means the
+ * n'th parenthesized subexpression.
+ * if n is larger than the number of parenthesized subexpressions,
+ * null is returned.
+ */
+ public string opIndex(size_t n)
+ {
+ if (n >= pmatch.length)
+ return null;
+ else
+ {
+ auto rm_so = pmatch[n].rm_so;
+ auto rm_eo = pmatch[n].rm_eo;
+ if (rm_so == rm_eo)
+ return null;
+ return input[rm_so .. rm_eo];
+ }
+ }
+
+ /**
+ Same as $(D_PARAM opIndex(n)).
+
+ WARNING:
+
+ Scheduled for deprecation due to confusion with overloaded
+ $(D_PARAM match(string)). Instead of $(D_PARAM regex.match(n))
+ you may want to use $(D_PARAM regex[n]).
+ */
+ public string match(size_t n)
+ {
+ return this[n];
+ }
+
+ /*******************
+ * Return the slice of the input that precedes the matched substring.
+ */
+ public @property string pre()
+ {
+ return input[0 .. pmatch[0].rm_so];
+ }
+
+ /*******************
+ * Return the slice of the input that follows the matched substring.
+ */
+ public @property string post()
+ {
+ return input[pmatch[0].rm_eo .. $];
+ }
+
+ uint re_nsub; // number of parenthesized subexpression matches
+ regmatch_t[] pmatch; // array [re_nsub + 1]
+
+ string input; // the string to search
+
+ // per instance:
+
+ string pattern; // source text of the regular expression
+
+ string flags; // source text of the attributes parameter
+
+ int errors;
+
+ uint attributes;
+
+ enum REA
+ {
+ global = 1, // has the g attribute
+ ignoreCase = 2, // has the i attribute
+ multiline = 4, // if treat as multiple lines separated
+ // by newlines, or as a single line
+ dotmatchlf = 8, // if . matches \n
+ }
+
+
+private:
+ size_t src; // current source index in input[]
+ size_t src_start; // starting index for match in input[]
+ size_t p; // position of parser in pattern[]
+ regmatch_t gmatch; // match for the entire regular expression
+ // (serves as storage for pmatch[0])
+
+ const(ubyte)[] program; // pattern[] compiled into regular expression program
+ OutBuffer buf;
+
+
+
+
+/******************************************/
+
+// Opcodes
+
+ enum : ubyte
+ {
+ REend, // end of program
+ REchar, // single character
+ REichar, // single character, case insensitive
+ REdchar, // single UCS character
+ REidchar, // single wide character, case insensitive
+ REanychar, // any character
+ REanystar, // ".*"
+ REstring, // string of characters
+ REistring, // string of characters, case insensitive
+ REtestbit, // any in bitmap, non-consuming
+ REbit, // any in the bit map
+ REnotbit, // any not in the bit map
+ RErange, // any in the string
+ REnotrange, // any not in the string
+ REor, // a | b
+ REplus, // 1 or more
+ REstar, // 0 or more
+ REquest, // 0 or 1
+ REnm, // n..m
+ REnmq, // n..m, non-greedy version
+ REbol, // beginning of line
+ REeol, // end of line
+ REparen, // parenthesized subexpression
+ REgoto, // goto offset
+
+ REwordboundary,
+ REnotwordboundary,
+ REdigit,
+ REnotdigit,
+ REspace,
+ REnotspace,
+ REword,
+ REnotword,
+ REbackref,
+ };
+
+// BUG: should this include '$'?
+ private int isword(dchar c) { return isAlphaNum(c) || c == '_'; }
+
+ private uint inf = ~0u;
+
+/* ********************************
+ * Throws RegExpException on error
+ */
+
+ public void compile(string pattern, string attributes)
+ {
+ //printf("RegExp.compile('%.*s', '%.*s')\n", pattern.length, pattern.ptr, attributes.length, attributes.ptr);
+
+ this.attributes = 0;
+ foreach (rchar c; attributes)
+ { REA att;
+
+ switch (c)
+ {
+ case 'g': att = REA.global; break;
+ case 'i': att = REA.ignoreCase; break;
+ case 'm': att = REA.multiline; break;
+ default:
+ error("unrecognized attribute");
+ return;
+ }
+ if (this.attributes & att)
+ { error("redundant attribute");
+ return;
+ }
+ this.attributes |= att;
+ }
+
+ input = null;
+
+ this.pattern = pattern;
+ this.flags = attributes;
+
+ uint oldre_nsub = re_nsub;
+ re_nsub = 0;
+ errors = 0;
+
+ buf = new OutBuffer();
+ buf.reserve(pattern.length * 8);
+ p = 0;
+ parseRegexp();
+ if (p < pattern.length)
+ { error("unmatched ')'");
+ }
+ // @@@ SKIPPING OPTIMIZATION SOLVES BUG 941 @@@
+ //optimize();
+ program = buf.data;
+ buf.data = null;
+ delete buf;
+
+ if (re_nsub > oldre_nsub)
+ {
+ if (pmatch.ptr is &gmatch)
+ pmatch = null;
+ pmatch.length = re_nsub + 1;
+ }
+ pmatch[0].rm_so = 0;
+ pmatch[0].rm_eo = 0;
+ }
+
+/********************************************
+ * Split s[] into an array of strings, using the regular
+ * expression as the separator.
+ * Returns:
+ * array of slices into s[]
+ */
+
+ public string[] split(string s)
+ {
+ debug(regexp) printf("regexp.split()\n");
+
+ string[] result;
+
+ if (s.length)
+ {
+ sizediff_t p, q;
+ for (q = p; q != s.length;)
+ {
+ if (test(s, q))
+ {
+ q = pmatch[0].rm_so;
+ auto e = pmatch[0].rm_eo;
+ if (e != p)
+ {
+ result ~= s[p .. q];
+ for (size_t i = 1; i < pmatch.length; i++)
+ {
+ auto so = pmatch[i].rm_so;
+ auto eo = pmatch[i].rm_eo;
+ if (so == eo)
+ { so = 0; // -1 gives array bounds error
+ eo = 0;
+ }
+ result ~= s[so .. eo];
+ }
+ q = p = e;
+ continue;
+ }
+ }
+ q++;
+ }
+ result ~= s[p .. s.length];
+ }
+ else if (!test(s))
+ result ~= s;
+ return result;
+ }
+
+ unittest
+ {
+ debug(regexp) printf("regexp.split.unittest()\n");
+
+ auto r = new RegExp("a*?", null);
+ string[] result;
+ string j;
+ int i;
+
+ result = r.split("ab");
+
+ assert(result.length == 2);
+ i = (result[0] == "a");
+ assert(i == 1);
+ i = (result[1] == "b");
+ assert(i == 1);
+
+ r = new RegExp("a*", null);
+ result = r.split("ab");
+ assert(result.length == 2);
+ i = (result[0] == "");
+ assert(i == 1);
+ i = (result[1] == "b");
+ assert(i == 1);
+
+ r = new RegExp("<(\\/)?([^<>]+)>", null);
+ result = r.split("a<b>font</b>bar<TAG>hello</TAG>");
+
+ debug(regexp)
+ {
+ for (i = 0; i < result.length; i++)
+ printf("result[%d] = '%.*s'\n", i, result[i].length, result[i].ptr);
+ }
+
+ j = join(result, ",");
+ //printf("j = '%.*s'\n", j.length, j.ptr);
+ i = (j == "a,,b,font,/,b,bar,,TAG,hello,/,TAG,");
+ assert(i == 1);
+
+ r = new RegExp("a[bc]", null);
+ result = r.match("123ab");
+ j = join(result, ",");
+ i = (j == "ab");
+ assert(i == 1);
+
+ result = r.match("ac");
+ j = join(result, ",");
+ i = (j == "ac");
+ assert(i == 1);
+ }
+
+/*************************************************
+ * Search string[] for match with regular expression.
+ * Returns:
+ * index of match if successful, -1 if not found
+ */
+
+ public sizediff_t find(string string)
+ {
+ if (test(string))
+ return pmatch[0].rm_so;
+ else
+ return -1; // no match
+ }
+
+//deprecated alias find search;
+
+ unittest
+ {
+ debug(regexp) printf("regexp.find.unittest()\n");
+
+ RegExp r = new RegExp("abc", null);
+ auto i = r.find("xabcy");
+ assert(i == 1);
+ i = r.find("cba");
+ assert(i == -1);
+ }
+
+
+/*************************************************
+ * Search s[] for match.
+ * Returns:
+ * If global attribute, return same value as exec(s).
+ * If not global attribute, return array of all matches.
+ */
+
+ public string[] match(string s)
+ {
+ string[] result;
+
+ if (attributes & REA.global)
+ {
+ sizediff_t lastindex = 0;
+
+ while (test(s, lastindex))
+ {
+ auto eo = pmatch[0].rm_eo;
+
+ result ~= input[pmatch[0].rm_so .. eo];
+ if (lastindex == eo)
+ lastindex++; // always consume some source
+ else
+ lastindex = eo;
+ }
+ }
+ else
+ {
+ result = exec(s);
+ }
+ return result;
+ }
+
+ unittest
+ {
+ debug(regexp) printf("regexp.match.unittest()\n");
+
+ int i;
+ string[] result;
+ string j;
+ RegExp r;
+
+ r = new RegExp("a[bc]", null);
+ result = r.match("1ab2ac3");
+ j = join(result, ",");
+ i = (j == "ab");
+ assert(i == 1);
+
+ r = new RegExp("a[bc]", "g");
+ result = r.match("1ab2ac3");
+ j = join(result, ",");
+ i = (j == "ab,ac");
+ assert(i == 1);
+ }
+
+
+/*************************************************
+ * Find regular expression matches in s[]. Replace those matches
+ * with a new string composed of format[] merged with the result of the
+ * matches.
+ * If global, replace all matches. Otherwise, replace first match.
+ * Returns: the new string
+ */
+
+ public string replace(string s, string format)
+ {
+ debug(regexp) printf("string = %.*s, format = %.*s\n", s.length, s.ptr, format.length, format.ptr);
+
+ string result = s;
+ sizediff_t lastindex = 0;
+ size_t offset = 0;
+
+ for (;;)
+ {
+ if (!test(s, lastindex))
+ break;
+
+ auto so = pmatch[0].rm_so;
+ auto eo = pmatch[0].rm_eo;
+
+ string replacement = replace(format);
+
+ // Optimize by using replace if possible - Dave Fladebo
+ string slice = result[offset + so .. offset + eo];
+ if (attributes & REA.global && // global, so replace all
+ !(attributes & REA.ignoreCase) && // not ignoring case
+ !(attributes & REA.multiline) && // not multiline
+ pattern == slice && // simple pattern (exact match, no special characters)
+ format == replacement) // simple format, not $ formats
+ {
+ debug(regexp)
+ {
+ auto sss = result[offset + so .. offset + eo];
+ printf("pattern: %.*s, slice: %.*s, format: %.*s, replacement: %.*s\n",
+ pattern.length, pattern.ptr, sss.length, sss.ptr, format.length, format.ptr, replacement.length, replacement.ptr);
+ }
+ result = std.array.replace(result,slice,replacement);
+ break;
+ }
+
+ result = replaceSlice(result, result[offset + so .. offset + eo], replacement);
+
+ if (attributes & REA.global)
+ {
+ offset += replacement.length - (eo - so);
+
+ if (lastindex == eo)
+ lastindex++; // always consume some source
+ else
+ lastindex = eo;
+ }
+ else
+ break;
+ }
+
+ return result;
+ }
+
+ unittest
+ {
+ debug(regexp) printf("regexp.replace.unittest()\n");
+
+ int i;
+ string result;
+ RegExp r;
+
+ r = new RegExp("a[bc]", "g");
+ result = r.replace("1ab2ac3", "x$&y");
+ i = (result == "1xaby2xacy3");
+ assert(i == 1);
+
+ r = new RegExp("ab", "g");
+ result = r.replace("1ab2ac3", "xy");
+ i = (result == "1xy2ac3");
+ assert(i == 1);
+ }
+
+
+/*************************************************
+ * Search string[] for match.
+ * Returns:
+ * array of slices into string[] representing matches
+ */
+
+ public string[] exec(string s)
+ {
+ debug(regexp) printf("regexp.exec(string = '%.*s')\n", s.length, s.ptr);
+ input = s;
+ pmatch[0].rm_so = 0;
+ pmatch[0].rm_eo = 0;
+ return exec();
+ }
+
+/*************************************************
+ * Pick up where last exec(string) or exec() left off,
+ * searching string[] for next match.
+ * Returns:
+ * array of slices into string[] representing matches
+ */
+
+ public string[] exec()
+ {
+ if (!test())
+ return null;
+
+ auto result = new string[pmatch.length];
+ for (int i = 0; i < pmatch.length; i++)
+ {
+ if (pmatch[i].rm_so == pmatch[i].rm_eo)
+ result[i] = null;
+ else
+ result[i] = input[pmatch[i].rm_so .. pmatch[i].rm_eo];
+ }
+
+ return result;
+ }
+
+/************************************************
+ * Search s[] for match.
+ * Returns: 0 for no match, !=0 for match
+ * Example:
+---
+import std.stdio;
+import undead.regexp;
+import std.string;
+
+int grep(int delegate(char[]) pred, char[][] list)
+{
+ int count;
+ foreach (s; list)
+ { if (pred(s))
+ ++count;
+ }
+ return count;
+}
+
+void main()
+{
+ auto x = grep(&RegExp("[Ff]oo").test,
+ std.string.split("mary had a foo lamb"));
+ writefln(x);
+}
+---
+* which prints: 1
+*/
+ //@@@
+public bool test(string s)
+ {
+ return test(s, 0 /*pmatch[0].rm_eo*/) != 0;
+ }
+
+/************************************************
+ * Pick up where last test(string) or test() left off, and search again.
+ * Returns: 0 for no match, !=0 for match
+ */
+
+ public int test()
+ {
+ return test(input, pmatch[0].rm_eo);
+ }
+
+/************************************************
+ * Test s[] starting at startindex against regular expression.
+ * Returns: 0 for no match, !=0 for match
+ */
+
+ public int test(string s, size_t startindex)
+ {
+ char firstc;
+
+ input = s;
+ debug (regexp) printf("RegExp.test(input[] = '%.*s', startindex = %zd)\n", input.length, input.ptr, startindex);
+ pmatch[0].rm_so = 0;
+ pmatch[0].rm_eo = 0;
+ if (startindex < 0 || startindex > input.length)
+ {
+ return 0; // fail
+ }
+ //debug(regexp) printProgram(program);
+
+ // First character optimization
+ firstc = 0;
+ if (program[0] == REchar)
+ {
+ firstc = program[1];
+ if (attributes & REA.ignoreCase && isAlpha(firstc))
+ firstc = 0;
+ }
+
+ for (auto si = startindex; ; si++)
+ {
+ if (firstc)
+ {
+ if (si == input.length)
+ break; // no match
+ if (input[si] != firstc)
+ {
+ si++;
+ if (!chr(si, firstc)) // if first character not found
+ break; // no match
+ }
+ }
+ for (size_t i = 0; i < re_nsub + 1; i++)
+ {
+ pmatch[i].rm_so = -1;
+ pmatch[i].rm_eo = -1;
+ }
+ src_start = src = si;
+ if (trymatch(0, program.length))
+ {
+ pmatch[0].rm_so = si;
+ pmatch[0].rm_eo = src;
+ //debug(regexp) printf("start = %d, end = %d\n", gmatch.rm_so, gmatch.rm_eo);
+ return 1;
+ }
+ // If possible match must start at beginning, we are done
+ if (program[0] == REbol || program[0] == REanystar)
+ {
+ if (attributes & REA.multiline)
+ {
+ // Scan for the next \n
+ if (!chr(si, '\n'))
+ break; // no match if '\n' not found
+ }
+ else
+ break;
+ }
+ if (si == input.length)
+ break;
+ debug(regexp)
+ {
+ auto sss = input[si + 1 .. input.length];
+ printf("Starting new try: '%.*s'\n", sss.length, sss.ptr);
+ }
+ }
+ return 0; // no match
+ }
+
+ /**
+ Returns whether string $(D_PARAM s) matches $(D_PARAM this).
+ */
+ alias test opEquals;
+// bool opEquals(string s)
+// {
+// return test(s);
+// }
+
+ unittest
+ {
+ assert("abc" == RegExp(".b."));
+ assert("abc" != RegExp(".b.."));
+ }
+
+ int chr(ref size_t si, rchar c)
+ {
+ for (; si < input.length; si++)
+ {
+ if (input[si] == c)
+ return 1;
+ }
+ return 0;
+ }
+
+
+ void printProgram(const(ubyte)[] prog)
+ {
+ //debug(regexp)
+ {
+ size_t len;
+ uint n;
+ uint m;
+ ushort *pu;
+ uint *puint;
+ char[] str;
+
+ printf("printProgram()\n");
+ for (size_t pc = 0; pc < prog.length; )
+ {
+ printf("%3d: ", pc);
+
+ //printf("prog[pc] = %d, REchar = %d, REnmq = %d\n", prog[pc], REchar, REnmq);
+ switch (prog[pc])
+ {
+ case REchar:
+ printf("\tREchar '%c'\n", prog[pc + 1]);
+ pc += 1 + char.sizeof;
+ break;
+
+ case REichar:
+ printf("\tREichar '%c'\n", prog[pc + 1]);
+ pc += 1 + char.sizeof;
+ break;
+
+ case REdchar:
+ printf("\tREdchar '%c'\n", *cast(dchar *)&prog[pc + 1]);
+ pc += 1 + dchar.sizeof;
+ break;
+
+ case REidchar:
+ printf("\tREidchar '%c'\n", *cast(dchar *)&prog[pc + 1]);
+ pc += 1 + dchar.sizeof;
+ break;
+
+ case REanychar:
+ printf("\tREanychar\n");
+ pc++;
+ break;
+
+ case REstring:
+ len = *cast(size_t *)&prog[pc + 1];
+ str = (cast(char*)&prog[pc + 1 + size_t.sizeof])[0 .. len];
+ printf("\tREstring x%x, '%.*s'\n", len, str.length, str.ptr);
+ pc += 1 + size_t.sizeof + len * rchar.sizeof;
+ break;
+
+ case REistring:
+ len = *cast(size_t *)&prog[pc + 1];
+ str = (cast(char*)&prog[pc + 1 + size_t.sizeof])[0 .. len];
+ printf("\tREistring x%x, '%.*s'\n", len, str.length, str.ptr);
+ pc += 1 + size_t.sizeof + len * rchar.sizeof;
+ break;
+
+ case REtestbit:
+ pu = cast(ushort *)&prog[pc + 1];
+ printf("\tREtestbit %d, %d\n", pu[0], pu[1]);
+ len = pu[1];
+ pc += 1 + 2 * ushort.sizeof + len;
+ break;
+
+ case REbit:
+ pu = cast(ushort *)&prog[pc + 1];
+ len = pu[1];
+ printf("\tREbit cmax=%02x, len=%d:", pu[0], len);
+ for (n = 0; n < len; n++)
+ printf(" %02x", prog[pc + 1 + 2 * ushort.sizeof + n]);
+ printf("\n");
+ pc += 1 + 2 * ushort.sizeof + len;
+ break;
+
+ case REnotbit:
+ pu = cast(ushort *)&prog[pc + 1];
+ printf("\tREnotbit %d, %d\n", pu[0], pu[1]);
+ len = pu[1];
+ pc += 1 + 2 * ushort.sizeof + len;
+ break;
+
+ case RErange:
+ len = *cast(uint *)&prog[pc + 1];
+ printf("\tRErange %d\n", len);
+ // BUG: REAignoreCase?
+ pc += 1 + uint.sizeof + len;
+ break;
+
+ case REnotrange:
+ len = *cast(uint *)&prog[pc + 1];
+ printf("\tREnotrange %d\n", len);
+ // BUG: REAignoreCase?
+ pc += 1 + uint.sizeof + len;
+ break;
+
+ case REbol:
+ printf("\tREbol\n");
+ pc++;
+ break;
+
+ case REeol:
+ printf("\tREeol\n");
+ pc++;
+ break;
+
+ case REor:
+ len = *cast(uint *)&prog[pc + 1];
+ printf("\tREor %d, pc=>%d\n", len, pc + 1 + uint.sizeof + len);
+ pc += 1 + uint.sizeof;
+ break;
+
+ case REgoto:
+ len = *cast(uint *)&prog[pc + 1];
+ printf("\tREgoto %d, pc=>%d\n", len, pc + 1 + uint.sizeof + len);
+ pc += 1 + uint.sizeof;
+ break;
+
+ case REanystar:
+ printf("\tREanystar\n");
+ pc++;
+ break;
+
+ case REnm:
+ case REnmq:
+ // len, n, m, ()
+ puint = cast(uint *)&prog[pc + 1];
+ len = puint[0];
+ n = puint[1];
+ m = puint[2];
+ printf("\tREnm%s len=%d, n=%u, m=%u, pc=>%d\n",
+ (prog[pc] == REnmq) ? "q".ptr : " ".ptr,
+ len, n, m, pc + 1 + uint.sizeof * 3 + len);
+ pc += 1 + uint.sizeof * 3;
+ break;
+
+ case REparen:
+ // len, n, ()
+ puint = cast(uint *)&prog[pc + 1];
+ len = puint[0];
+ n = puint[1];
+ printf("\tREparen len=%d n=%d, pc=>%d\n", len, n, pc + 1 + uint.sizeof * 2 + len);
+ pc += 1 + uint.sizeof * 2;
+ break;
+
+ case REend:
+ printf("\tREend\n");
+ return;
+
+ case REwordboundary:
+ printf("\tREwordboundary\n");
+ pc++;
+ break;
+
+ case REnotwordboundary:
+ printf("\tREnotwordboundary\n");
+ pc++;
+ break;
+
+ case REdigit:
+ printf("\tREdigit\n");
+ pc++;
+ break;
+
+ case REnotdigit:
+ printf("\tREnotdigit\n");
+ pc++;
+ break;
+
+ case REspace:
+ printf("\tREspace\n");
+ pc++;
+ break;
+
+ case REnotspace:
+ printf("\tREnotspace\n");
+ pc++;
+ break;
+
+ case REword:
+ printf("\tREword\n");
+ pc++;
+ break;
+
+ case REnotword:
+ printf("\tREnotword\n");
+ pc++;
+ break;
+
+ case REbackref:
+ printf("\tREbackref %d\n", prog[1]);
+ pc += 2;
+ break;
+
+ default:
+ assert(0);
+ }
+ }
+ }
+ }
+
+
+/**************************************************
+ * Match input against a section of the program[].
+ * Returns:
+ * 1 if successful match
+ * 0 no match
+ */
+
+ int trymatch(size_t pc, size_t pcend)
+ {
+ size_t len;
+ size_t n;
+ size_t m;
+ size_t count;
+ size_t pop;
+ size_t ss;
+ regmatch_t *psave;
+ size_t c1;
+ size_t c2;
+ ushort* pu;
+ uint* puint;
+
+ debug(regexp)
+ {
+ auto sss = input[src .. input.length];
+ printf("RegExp.trymatch(pc = %zd, src = '%.*s', pcend = %zd)\n", pc, sss.length, sss.ptr, pcend);
+ }
+ auto srcsave = src;
+ psave = null;
+ for (;;)
+ {
+ if (pc == pcend) // if done matching
+ { debug(regex) printf("\tprogend\n");
+ return 1;
+ }
+
+ //printf("\top = %d\n", program[pc]);
+ switch (program[pc])
+ {
+ case REchar:
+ if (src == input.length)
+ goto Lnomatch;
+ debug(regexp) printf("\tREchar '%c', src = '%c'\n", program[pc + 1], input[src]);
+ if (program[pc + 1] != input[src])
+ goto Lnomatch;
+ src++;
+ pc += 1 + char.sizeof;
+ break;
+
+ case REichar:
+ if (src == input.length)
+ goto Lnomatch;
+ debug(regexp) printf("\tREichar '%c', src = '%c'\n", program[pc + 1], input[src]);
+ c1 = program[pc + 1];
+ c2 = input[src];
+ if (c1 != c2)
+ {
+ if (isLower(cast(rchar)c2))
+ c2 = std.ascii.toUpper(cast(rchar)c2);
+ else
+ goto Lnomatch;
+ if (c1 != c2)
+ goto Lnomatch;
+ }
+ src++;
+ pc += 1 + char.sizeof;
+ break;
+
+ case REdchar:
+ debug(regexp) printf("\tREdchar '%c', src = '%c'\n", *(cast(dchar *)&program[pc + 1]), input[src]);
+ if (src == input.length)
+ goto Lnomatch;
+ if (*(cast(dchar *)&program[pc + 1]) != input[src])
+ goto Lnomatch;
+ src++;
+ pc += 1 + dchar.sizeof;
+ break;
+
+ case REidchar:
+ debug(regexp) printf("\tREidchar '%c', src = '%c'\n", *(cast(dchar *)&program[pc + 1]), input[src]);
+ if (src == input.length)
+ goto Lnomatch;
+ c1 = *(cast(dchar *)&program[pc + 1]);
+ c2 = input[src];
+ if (c1 != c2)
+ {
+ if (isLower(cast(rchar)c2))
+ c2 = std.ascii.toUpper(cast(rchar)c2);
+ else
+ goto Lnomatch;
+ if (c1 != c2)
+ goto Lnomatch;
+ }
+ src++;
+ pc += 1 + dchar.sizeof;
+ break;
+
+ case REanychar:
+ debug(regexp) printf("\tREanychar\n");
+ if (src == input.length)
+ goto Lnomatch;
+ if (!(attributes & REA.dotmatchlf) && input[src] == cast(rchar)'\n')
+ goto Lnomatch;
+ src += std.utf.stride(input, src);
+ //src++;
+ pc++;
+ break;
+
+ case REstring:
+ len = *cast(size_t *)&program[pc + 1];
+ debug(regexp)
+ {
+ auto sss2 = (&program[pc + 1 + size_t.sizeof])[0 .. len];
+ printf("\tREstring x%x, '%.*s'\n", len, sss2.length, sss2.ptr);
+ }
+ if (src + len > input.length)
+ goto Lnomatch;
+ if (memcmp(&program[pc + 1 + size_t.sizeof], &input[src], len * rchar.sizeof))
+ goto Lnomatch;
+ src += len;
+ pc += 1 + size_t.sizeof + len * rchar.sizeof;
+ break;
+
+ case REistring:
+ len = *cast(size_t *)&program[pc + 1];
+ debug(regexp)
+ {
+ auto sss2 = (&program[pc + 1 + size_t.sizeof])[0 .. len];
+ printf("\tREistring x%x, '%.*s'\n", len, sss2.length, sss2.ptr);
+ }
+ if (src + len > input.length)
+ goto Lnomatch;
+ if (icmp((cast(char*)&program[pc + 1 + size_t.sizeof])[0..len],
+ input[src .. src + len]))
+ goto Lnomatch;
+ src += len;
+ pc += 1 + size_t.sizeof + len * rchar.sizeof;
+ break;
+
+ case REtestbit:
+ pu = (cast(ushort *)&program[pc + 1]);
+ if (src == input.length)
+ goto Lnomatch;
+ debug(regexp) printf("\tREtestbit %d, %d, '%c', x%02x\n",
+ pu[0], pu[1], input[src], input[src]);
+ len = pu[1];
+ c1 = input[src];
+ //printf("[x%02x]=x%02x, x%02x\n", c1 >> 3, ((&program[pc + 1 + 4])[c1 >> 3] ), (1 << (c1 & 7)));
+ if (c1 <= pu[0] &&
+ !((&(program[pc + 1 + 4]))[c1 >> 3] & (1 << (c1 & 7))))
+ goto Lnomatch;
+ pc += 1 + 2 * ushort.sizeof + len;
+ break;
+
+ case REbit:
+ pu = (cast(ushort *)&program[pc + 1]);
+ if (src == input.length)
+ goto Lnomatch;
+ debug(regexp) printf("\tREbit %d, %d, '%c'\n",
+ pu[0], pu[1], input[src]);
+ len = pu[1];
+ c1 = input[src];
+ if (c1 > pu[0])
+ goto Lnomatch;
+ if (!((&program[pc + 1 + 4])[c1 >> 3] & (1 << (c1 & 7))))
+ goto Lnomatch;
+ src++;
+ pc += 1 + 2 * ushort.sizeof + len;
+ break;
+
+ case REnotbit:
+ pu = (cast(ushort *)&program[pc + 1]);
+ if (src == input.length)
+ goto Lnomatch;
+ debug(regexp) printf("\tREnotbit %d, %d, '%c'\n",
+ pu[0], pu[1], input[src]);
+ len = pu[1];
+ c1 = input[src];
+ if (c1 <= pu[0] &&
+ ((&program[pc + 1 + 4])[c1 >> 3] & (1 << (c1 & 7))))
+ goto Lnomatch;
+ src++;
+ pc += 1 + 2 * ushort.sizeof + len;
+ break;
+
+ case RErange:
+ len = *cast(uint *)&program[pc + 1];
+ debug(regexp) printf("\tRErange %d\n", len);
+ if (src == input.length)
+ goto Lnomatch;
+ // BUG: REA.ignoreCase?
+ if (memchr(cast(char*)&program[pc + 1 + uint.sizeof], input[src], len) == null)
+ goto Lnomatch;
+ src++;
+ pc += 1 + uint.sizeof + len;
+ break;
+
+ case REnotrange:
+ len = *cast(uint *)&program[pc + 1];
+ debug(regexp) printf("\tREnotrange %d\n", len);
+ if (src == input.length)
+ goto Lnomatch;
+ // BUG: REA.ignoreCase?
+ if (memchr(cast(char*)&program[pc + 1 + uint.sizeof], input[src], len) != null)
+ goto Lnomatch;
+ src++;
+ pc += 1 + uint.sizeof + len;
+ break;
+
+ case REbol:
+ debug(regexp) printf("\tREbol\n");
+ if (src == 0)
+ {
+ }
+ else if (attributes & REA.multiline)
+ {
+ if (input[src - 1] != '\n')
+ goto Lnomatch;
+ }
+ else
+ goto Lnomatch;
+ pc++;
+ break;
+
+ case REeol:
+ debug(regexp) printf("\tREeol\n");
+ if (src == input.length)
+ {
+ }
+ else if (attributes & REA.multiline && input[src] == '\n')
+ src++;
+ else
+ goto Lnomatch;
+ pc++;
+ break;
+
+ case REor:
+ len = (cast(uint *)&program[pc + 1])[0];
+ debug(regexp) printf("\tREor %d\n", len);
+ pop = pc + 1 + uint.sizeof;
+ ss = src;
+ if (trymatch(pop, pcend))
+ {
+ if (pcend != program.length)
+ {
+ auto s = src;
+ if (trymatch(pcend, program.length))
+ { debug(regexp) printf("\tfirst operand matched\n");
+ src = s;
+ return 1;
+ }
+ else
+ {
+ // If second branch doesn't match to end, take first anyway
+ src = ss;
+ if (!trymatch(pop + len, program.length))
+ {
+ debug(regexp) printf("\tfirst operand matched\n");
+ src = s;
+ return 1;
+ }
+ }
+ src = ss;
+ }
+ else
+ { debug(regexp) printf("\tfirst operand matched\n");
+ return 1;
+ }
+ }
+ pc = pop + len; // proceed with 2nd branch
+ break;
+
+ case REgoto:
+ debug(regexp) printf("\tREgoto\n");
+ len = (cast(uint *)&program[pc + 1])[0];
+ pc += 1 + uint.sizeof + len;
+ break;
+
+ case REanystar:
+ debug(regexp) printf("\tREanystar\n");
+ pc++;
+ for (;;)
+ {
+ auto s1 = src;
+ if (src == input.length)
+ break;
+ if (!(attributes & REA.dotmatchlf) && input[src] == '\n')
+ break;
+ src++;
+ auto s2 = src;
+
+ // If no match after consumption, but it
+ // did match before, then no match
+ if (!trymatch(pc, program.length))
+ {
+ src = s1;
+ // BUG: should we save/restore pmatch[]?
+ if (trymatch(pc, program.length))
+ {
+ src = s1; // no match
+ break;
+ }
+ }
+ src = s2;
+ }
+ break;
+
+ case REnm:
+ case REnmq:
+ // len, n, m, ()
+ puint = cast(uint *)&program[pc + 1];
+ len = puint[0];
+ n = puint[1];
+ m = puint[2];
+ debug(regexp) printf("\tREnm%s len=%d, n=%u, m=%u\n",
+ (program[pc] == REnmq) ? "q".ptr : "".ptr, len, n, m);
+ pop = pc + 1 + uint.sizeof * 3;
+ for (count = 0; count < n; count++)
+ {
+ if (!trymatch(pop, pop + len))
+ goto Lnomatch;
+ }
+ if (!psave && count < m)
+ {
+ //version (Win32)
+ psave = cast(regmatch_t *)alloca((re_nsub + 1) * regmatch_t.sizeof);
+ //else
+ //psave = new regmatch_t[re_nsub + 1];
+ }
+ if (program[pc] == REnmq) // if minimal munch
+ {
+ for (; count < m; count++)
+ {
+ memcpy(psave, pmatch.ptr, (re_nsub + 1) * regmatch_t.sizeof);
+ auto s1 = src;
+
+ if (trymatch(pop + len, program.length))
+ {
+ src = s1;
+ memcpy(pmatch.ptr, psave, (re_nsub + 1) * regmatch_t.sizeof);
+ break;
+ }
+
+ if (!trymatch(pop, pop + len))
+ { debug(regexp) printf("\tdoesn't match subexpression\n");
+ break;
+ }
+
+ // If source is not consumed, don't
+ // infinite loop on the match
+ if (s1 == src)
+ { debug(regexp) printf("\tsource is not consumed\n");
+ break;
+ }
+ }
+ }
+ else // maximal munch
+ {
+ for (; count < m; count++)
+ {
+ memcpy(psave, pmatch.ptr, (re_nsub + 1) * regmatch_t.sizeof);
+ auto s1 = src;
+ if (!trymatch(pop, pop + len))
+ { debug(regexp) printf("\tdoesn't match subexpression\n");
+ break;
+ }
+ auto s2 = src;
+
+ // If source is not consumed, don't
+ // infinite loop on the match
+ if (s1 == s2)
+ { debug(regexp) printf("\tsource is not consumed\n");
+ break;
+ }
+
+ // If no match after consumption, but it
+ // did match before, then no match
+ if (!trymatch(pop + len, program.length))
+ {
+ src = s1;
+ if (trymatch(pop + len, program.length))
+ {
+ src = s1; // no match
+ memcpy(pmatch.ptr, psave, (re_nsub + 1) * regmatch_t.sizeof);
+ break;
+ }
+ }
+ src = s2;
+ }
+ }
+ debug(regexp) printf("\tREnm len=%d, n=%u, m=%u, DONE count=%d\n", len, n, m, count);
+ pc = pop + len;
+ break;
+
+ case REparen:
+ // len, ()
+ debug(regexp) printf("\tREparen\n");
+ puint = cast(uint *)&program[pc + 1];
+ len = puint[0];
+ n = puint[1];
+ pop = pc + 1 + uint.sizeof * 2;
+ ss = src;
+ if (!trymatch(pop, pop + len))
+ goto Lnomatch;
+ pmatch[n + 1].rm_so = ss;
+ pmatch[n + 1].rm_eo = src;
+ pc = pop + len;
+ break;
+
+ case REend:
+ debug(regexp) printf("\tREend\n");
+ return 1; // successful match
+
+ case REwordboundary:
+ debug(regexp) printf("\tREwordboundary\n");
+ if (src > 0 && src < input.length)
+ {
+ c1 = input[src - 1];
+ c2 = input[src];
+ if (!(
+ (isword(cast(rchar)c1) && !isword(cast(rchar)c2)) ||
+ (!isword(cast(rchar)c1) && isword(cast(rchar)c2))
+ )
+ )
+ goto Lnomatch;
+ }
+ pc++;
+ break;
+
+ case REnotwordboundary:
+ debug(regexp) printf("\tREnotwordboundary\n");
+ if (src == 0 || src == input.length)
+ goto Lnomatch;
+ c1 = input[src - 1];
+ c2 = input[src];
+ if (
+ (isword(cast(rchar)c1) && !isword(cast(rchar)c2)) ||
+ (!isword(cast(rchar)c1) && isword(cast(rchar)c2))
+ )
+ goto Lnomatch;
+ pc++;
+ break;
+
+ case REdigit:
+ debug(regexp) printf("\tREdigit\n");
+ if (src == input.length)
+ goto Lnomatch;
+ if (!isDigit(input[src]))
+ goto Lnomatch;
+ src++;
+ pc++;
+ break;
+
+ case REnotdigit:
+ debug(regexp) printf("\tREnotdigit\n");
+ if (src == input.length)
+ goto Lnomatch;
+ if (isDigit(input[src]))
+ goto Lnomatch;
+ src++;
+ pc++;
+ break;
+
+ case REspace:
+ debug(regexp) printf("\tREspace\n");
+ if (src == input.length)
+ goto Lnomatch;
+ if (!isWhite(input[src]))
+ goto Lnomatch;
+ src++;
+ pc++;
+ break;
+
+ case REnotspace:
+ debug(regexp) printf("\tREnotspace\n");
+ if (src == input.length)
+ goto Lnomatch;
+ if (isWhite(input[src]))
+ goto Lnomatch;
+ src++;
+ pc++;
+ break;
+
+ case REword:
+ debug(regexp) printf("\tREword\n");
+ if (src == input.length)
+ goto Lnomatch;
+ if (!isword(input[src]))
+ goto Lnomatch;
+ src++;
+ pc++;
+ break;
+
+ case REnotword:
+ debug(regexp) printf("\tREnotword\n");
+ if (src == input.length)
+ goto Lnomatch;
+ if (isword(input[src]))
+ goto Lnomatch;
+ src++;
+ pc++;
+ break;
+
+ case REbackref:
+ {
+ n = program[pc + 1];
+ debug(regexp) printf("\tREbackref %d\n", n);
+
+ auto so = pmatch[n + 1].rm_so;
+ auto eo = pmatch[n + 1].rm_eo;
+ len = eo - so;
+ if (src + len > input.length)
+ goto Lnomatch;
+ else if (attributes & REA.ignoreCase)
+ {
+ if (icmp(input[src .. src + len], input[so .. eo]))
+ goto Lnomatch;
+ }
+ else if (memcmp(&input[src], &input[so], len * rchar.sizeof))
+ goto Lnomatch;
+ src += len;
+ pc += 2;
+ break;
+ }
+
+ default:
+ assert(0);
+ }
+ }
+
+ Lnomatch:
+ debug(regexp) printf("\tnomatch pc=%d\n", pc);
+ src = srcsave;
+ return 0;
+ }
+
+/* =================== Compiler ================== */
+
+ int parseRegexp()
+ {
+ size_t gotooffset;
+ uint len1;
+ uint len2;
+
+ debug(regexp)
+ {
+ auto sss = pattern[p .. pattern.length];
+ printf("parseRegexp() '%.*s'\n", sss.length, sss.ptr);
+ }
+ auto offset = buf.offset;
+ for (;;)
+ {
+ assert(p <= pattern.length);
+ if (p == pattern.length)
+ { buf.write(REend);
+ return 1;
+ }
+ switch (pattern[p])
+ {
+ case ')':
+ return 1;
+
+ case '|':
+ p++;
+ gotooffset = buf.offset;
+ buf.write(REgoto);
+ buf.write(cast(uint)0);
+ len1 = cast(uint)(buf.offset - offset);
+ buf.spread(offset, 1 + uint.sizeof);
+ gotooffset += 1 + uint.sizeof;
+ parseRegexp();
+ len2 = cast(uint)(buf.offset - (gotooffset + 1 + uint.sizeof));
+ buf.data[offset] = REor;
+ (cast(uint *)&buf.data[offset + 1])[0] = len1;
+ (cast(uint *)&buf.data[gotooffset + 1])[0] = len2;
+ break;
+
+ default:
+ parsePiece();
+ break;
+ }
+ }
+ }
+
+ int parsePiece()
+ {
+ uint len;
+ uint n;
+ uint m;
+ ubyte op;
+ auto plength = pattern.length;
+
+ debug(regexp)
+ {
+ auto sss = pattern[p .. pattern.length];
+ printf("parsePiece() '%.*s'\n", sss.length, sss.ptr);
+ }
+ auto offset = buf.offset;
+ parseAtom();
+ if (p == plength)
+ return 1;
+ switch (pattern[p])
+ {
+ case '*':
+ // Special optimization: replace .* with REanystar
+ if (buf.offset - offset == 1 &&
+ buf.data[offset] == REanychar &&
+ p + 1 < plength &&
+ pattern[p + 1] != '?')
+ {
+ buf.data[offset] = REanystar;
+ p++;
+ break;
+ }
+
+ n = 0;
+ m = inf;
+ goto Lnm;
+
+ case '+':
+ n = 1;
+ m = inf;
+ goto Lnm;
+
+ case '?':
+ n = 0;
+ m = 1;
+ goto Lnm;
+
+ case '{': // {n} {n,} {n,m}
+ p++;
+ if (p == plength || !isDigit(pattern[p]))
+ goto Lerr;
+ n = 0;
+ do
+ {
+ // BUG: handle overflow
+ n = n * 10 + pattern[p] - '0';
+ p++;
+ if (p == plength)
+ goto Lerr;
+ } while (isDigit(pattern[p]));
+ if (pattern[p] == '}') // {n}
+ { m = n;
+ goto Lnm;
+ }
+ if (pattern[p] != ',')
+ goto Lerr;
+ p++;
+ if (p == plength)
+ goto Lerr;
+ if (pattern[p] == /*{*/ '}') // {n,}
+ { m = inf;
+ goto Lnm;
+ }
+ if (!isDigit(pattern[p]))
+ goto Lerr;
+ m = 0; // {n,m}
+ do
+ {
+ // BUG: handle overflow
+ m = m * 10 + pattern[p] - '0';
+ p++;
+ if (p == plength)
+ goto Lerr;
+ } while (isDigit(pattern[p]));
+ if (pattern[p] != /*{*/ '}')
+ goto Lerr;
+ goto Lnm;
+
+ Lnm:
+ p++;
+ op = REnm;
+ if (p < plength && pattern[p] == '?')
+ { op = REnmq; // minimal munch version
+ p++;
+ }
+ len = cast(uint)(buf.offset - offset);
+ buf.spread(offset, 1 + uint.sizeof * 3);
+ buf.data[offset] = op;
+ uint* puint = cast(uint *)&buf.data[offset + 1];
+ puint[0] = len;
+ puint[1] = n;
+ puint[2] = m;
+ break;
+
+ default:
+ break;
+ }
+ return 1;
+
+ Lerr:
+ error("badly formed {n,m}");
+ assert(0);
+ }
+
+ int parseAtom()
+ { ubyte op;
+ size_t offset;
+ rchar c;
+
+ debug(regexp)
+ {
+ auto sss = pattern[p .. pattern.length];
+ printf("parseAtom() '%.*s'\n", sss.length, sss.ptr);
+ }
+ if (p < pattern.length)
+ {
+ c = pattern[p];
+ switch (c)
+ {
+ case '*':
+ case '+':
+ case '?':
+ error("*+? not allowed in atom");
+ p++;
+ return 0;
+
+ case '(':
+ p++;
+ buf.write(REparen);
+ offset = buf.offset;
+ buf.write(cast(uint)0); // reserve space for length
+ buf.write(re_nsub);
+ re_nsub++;
+ parseRegexp();
+ *cast(uint *)&buf.data[offset] =
+ cast(uint)(buf.offset - (offset + uint.sizeof * 2));
+ if (p == pattern.length || pattern[p] != ')')
+ {
+ error("')' expected");
+ return 0;
+ }
+ p++;
+ break;
+
+ case '[':
+ if (!parseRange())
+ return 0;
+ break;
+
+ case '.':
+ p++;
+ buf.write(REanychar);
+ break;
+
+ case '^':
+ p++;
+ buf.write(REbol);
+ break;
+
+ case '$':
+ p++;
+ buf.write(REeol);
+ break;
+
+ case '\\':
+ p++;
+ if (p == pattern.length)
+ { error("no character past '\\'");
+ return 0;
+ }
+ c = pattern[p];
+ switch (c)
+ {
+ case 'b': op = REwordboundary; goto Lop;
+ case 'B': op = REnotwordboundary; goto Lop;
+ case 'd': op = REdigit; goto Lop;
+ case 'D': op = REnotdigit; goto Lop;
+ case 's': op = REspace; goto Lop;
+ case 'S': op = REnotspace; goto Lop;
+ case 'w': op = REword; goto Lop;
+ case 'W': op = REnotword; goto Lop;
+
+ Lop:
+ buf.write(op);
+ p++;
+ break;
+
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ case 'c':
+ case 'x':
+ case 'u':
+ case '0':
+ c = cast(char)escape();
+ goto Lbyte;
+
+ case '1': case '2': case '3':
+ case '4': case '5': case '6':
+ case '7': case '8': case '9':
+ c -= '1';
+ if (c < re_nsub)
+ { buf.write(REbackref);
+ buf.write(cast(ubyte)c);
+ }
+ else
+ { error("no matching back reference");
+ return 0;
+ }
+ p++;
+ break;
+
+ default:
+ p++;
+ goto Lbyte;
+ }
+ break;
+
+ default:
+ p++;
+ Lbyte:
+ op = REchar;
+ if (attributes & REA.ignoreCase)
+ {
+ if (isAlpha(c))
+ {
+ op = REichar;
+ c = cast(char)std.ascii.toUpper(c);
+ }
+ }
+ if (op == REchar && c <= 0xFF)
+ {
+ // Look ahead and see if we can make this into
+ // an REstring
+ auto q = p;
+ for (; q < pattern.length; ++q)
+ { rchar qc = pattern[q];
+
+ switch (qc)
+ {
+ case '{':
+ case '*':
+ case '+':
+ case '?':
+ if (q == p)
+ goto Lchar;
+ q--;
+ break;
+
+ case '(': case ')':
+ case '|':
+ case '[': case ']':
+ case '.': case '^':
+ case '$': case '\\':
+ case '}':
+ break;
+
+ default:
+ continue;
+ }
+ break;
+ }
+ auto len = q - p;
+ if (len > 0)
+ {
+ debug(regexp) printf("writing string len %d, c = '%c', pattern[p] = '%c'\n", len+1, c, pattern[p]);
+ buf.reserve(5 + (1 + len) * rchar.sizeof);
+ buf.write((attributes & REA.ignoreCase) ? REistring : REstring);
+ buf.write(len + 1);
+ buf.write(c);
+ buf.write(pattern[p .. p + len]);
+ p = q;
+ break;
+ }
+ }
+ if (c >= 0x80)
+ {
+ // Convert to dchar opcode
+ op = (op == REchar) ? REdchar : REidchar;
+ buf.write(op);
+ buf.write(c);
+ }
+ else
+ {
+ Lchar:
+ debug(regexp) printf("It's an REchar '%c'\n", c);
+ buf.write(op);
+ buf.write(cast(char)c);
+ }
+ break;
+ }
+ }
+ return 1;
+ }
+
+private:
+ class Range
+ {
+ size_t maxc;
+ size_t maxb;
+ OutBuffer buf;
+ ubyte* base;
+ BitArray bits;
+
+ this(OutBuffer buf)
+ {
+ this.buf = buf;
+ if (buf.data.length)
+ this.base = &buf.data[buf.offset];
+ }
+
+ void setbitmax(size_t u)
+ {
+ //printf("setbitmax(x%x), maxc = x%x\n", u, maxc);
+ if (u > maxc)
+ {
+ maxc = u;
+ auto b = u / 8;
+ if (b >= maxb)
+ {
+ auto u2 = base ? base - &buf.data[0] : 0;
+ buf.fill0(b - maxb + 1);
+ base = &buf.data[u2];
+ maxb = b + 1;
+ //bits = (cast(bit*)this.base)[0 .. maxc + 1];
+ bits = BitArray(maxc + 1, cast(size_t*)this.base);
+ }
+ bits.length = maxc + 1;
+ }
+ }
+
+ void setbit2(size_t u)
+ {
+ setbitmax(u + 1);
+ //printf("setbit2 [x%02x] |= x%02x\n", u >> 3, 1 << (u & 7));
+ bits[u] = 1;
+ }
+
+ };
+
+ int parseRange()
+ {
+ int c;
+ int c2;
+ uint i;
+ uint cmax;
+
+ cmax = 0x7F;
+ p++;
+ ubyte op = REbit;
+ if (p == pattern.length)
+ {
+ error("invalid range");
+ return 0;
+ }
+ if (pattern[p] == '^')
+ { p++;
+ op = REnotbit;
+ if (p == pattern.length)
+ {
+ error("invalid range");
+ return 0;
+ }
+ }
+ buf.write(op);
+ auto offset = buf.offset;
+ buf.write(cast(uint)0); // reserve space for length
+ buf.reserve(128 / 8);
+ auto r = new Range(buf);
+ if (op == REnotbit)
+ r.setbit2(0);
+ switch (pattern[p])
+ {
+ case ']':
+ case '-':
+ c = pattern[p];
+ p++;
+ r.setbit2(c);
+ break;
+
+ default:
+ break;
+ }
+
+ enum RS { start, rliteral, dash }
+ RS rs;
+
+ rs = RS.start;
+ for (;;)
+ {
+ if (p == pattern.length)
+ goto Lerr;
+ switch (pattern[p])
+ {
+ case ']':
+ switch (rs)
+ { case RS.dash:
+ r.setbit2('-');
+ goto case;
+ case RS.rliteral:
+ r.setbit2(c);
+ break;
+ case RS.start:
+ break;
+ default:
+ assert(0);
+ }
+ p++;
+ break;
+
+ case '\\':
+ p++;
+ r.setbitmax(cmax);
+ if (p == pattern.length)
+ goto Lerr;
+ switch (pattern[p])
+ {
+ case 'd':
+ for (i = '0'; i <= '9'; i++)
+ r.bits[i] = 1;
+ goto Lrs;
+
+ case 'D':
+ for (i = 1; i < '0'; i++)
+ r.bits[i] = 1;
+ for (i = '9' + 1; i <= cmax; i++)
+ r.bits[i] = 1;
+ goto Lrs;
+
+ case 's':
+ for (i = 0; i <= cmax; i++)
+ if (isWhite(i))
+ r.bits[i] = 1;
+ goto Lrs;
+
+ case 'S':
+ for (i = 1; i <= cmax; i++)
+ if (!isWhite(i))
+ r.bits[i] = 1;
+ goto Lrs;
+
+ case 'w':
+ for (i = 0; i <= cmax; i++)
+ if (isword(cast(rchar)i))
+ r.bits[i] = 1;
+ goto Lrs;
+
+ case 'W':
+ for (i = 1; i <= cmax; i++)
+ if (!isword(cast(rchar)i))
+ r.bits[i] = 1;
+ goto Lrs;
+
+ Lrs:
+ switch (rs)
+ { case RS.dash:
+ r.setbit2('-');
+ goto case;
+ case RS.rliteral:
+ r.setbit2(c);
+ break;
+ default:
+ break;
+ }
+ rs = RS.start;
+ continue;
+
+ default:
+ break;
+ }
+ c2 = escape();
+ goto Lrange;
+
+ case '-':
+ p++;
+ if (rs == RS.start)
+ goto Lrange;
+ else if (rs == RS.rliteral)
+ rs = RS.dash;
+ else if (rs == RS.dash)
+ {
+ r.setbit2(c);
+ r.setbit2('-');
+ rs = RS.start;
+ }
+ continue;
+
+ default:
+ c2 = pattern[p];
+ p++;
+ Lrange:
+ switch (rs)
+ { case RS.rliteral:
+ r.setbit2(c);
+ goto case;
+ case RS.start:
+ c = c2;
+ rs = RS.rliteral;
+ break;
+
+ case RS.dash:
+ if (c > c2)
+ { error("inverted range in character class");
+ return 0;
+ }
+ r.setbitmax(c2);
+ //printf("c = %x, c2 = %x\n",c,c2);
+ for (; c <= c2; c++)
+ r.bits[c] = 1;
+ rs = RS.start;
+ break;
+
+ default:
+ assert(0);
+ }
+ continue;
+ }
+ break;
+ }
+ if (attributes & REA.ignoreCase)
+ {
+ // BUG: what about dchar?
+ r.setbitmax(0x7F);
+ for (c = 'a'; c <= 'z'; c++)
+ {
+ if (r.bits[c])
+ r.bits[c + 'A' - 'a'] = 1;
+ else if (r.bits[c + 'A' - 'a'])
+ r.bits[c] = 1;
+ }
+ }
+ //printf("maxc = %d, maxb = %d\n",r.maxc,r.maxb);
+ (cast(ushort *)&buf.data[offset])[0] = cast(ushort)r.maxc;
+ (cast(ushort *)&buf.data[offset])[1] = cast(ushort)r.maxb;
+ return 1;
+
+ Lerr:
+ error("invalid range");
+ return 0;
+ }
+
+ void error(string msg)
+ {
+ errors++;
+ debug(regexp) printf("error: %.*s\n", msg.length, msg.ptr);
+//assert(0);
+//*(char*)0=0;
+ throw new RegExpException(msg);
+ }
+
+// p is following the \ char
+ int escape()
+ in
+ {
+ assert(p < pattern.length);
+ }
+ body
+ { int c;
+ int i;
+ rchar tc;
+
+ c = pattern[p]; // none of the cases are multibyte
+ switch (c)
+ {
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+
+ // BUG: Perl does \a and \e too, should we?
+
+ case 'c':
+ ++p;
+ if (p == pattern.length)
+ goto Lretc;
+ c = pattern[p];
+ // Note: we are deliberately not allowing dchar letters
+ if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')))
+ {
+ Lcerr:
+ error("letter expected following \\c");
+ return 0;
+ }
+ c &= 0x1F;
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ c -= '0';
+ for (i = 0; i < 2; i++)
+ {
+ p++;
+ if (p == pattern.length)
+ goto Lretc;
+ tc = pattern[p];
+ if ('0' <= tc && tc <= '7')
+ { c = c * 8 + (tc - '0');
+ // Treat overflow as if last
+ // digit was not an octal digit
+ if (c >= 0xFF)
+ { c >>= 3;
+ return c;
+ }
+ }
+ else
+ return c;
+ }
+ break;
+
+ case 'x':
+ c = 0;
+ for (i = 0; i < 2; i++)
+ {
+ p++;
+ if (p == pattern.length)
+ goto Lretc;
+ tc = pattern[p];
+ if ('0' <= tc && tc <= '9')
+ c = c * 16 + (tc - '0');
+ else if ('a' <= tc && tc <= 'f')
+ c = c * 16 + (tc - 'a' + 10);
+ else if ('A' <= tc && tc <= 'F')
+ c = c * 16 + (tc - 'A' + 10);
+ else if (i == 0) // if no hex digits after \x
+ {
+ // Not a valid \xXX sequence
+ return 'x';
+ }
+ else
+ return c;
+ }
+ break;
+
+ case 'u':
+ c = 0;
+ for (i = 0; i < 4; i++)
+ {
+ p++;
+ if (p == pattern.length)
+ goto Lretc;
+ tc = pattern[p];
+ if ('0' <= tc && tc <= '9')
+ c = c * 16 + (tc - '0');
+ else if ('a' <= tc && tc <= 'f')
+ c = c * 16 + (tc - 'a' + 10);
+ else if ('A' <= tc && tc <= 'F')
+ c = c * 16 + (tc - 'A' + 10);
+ else
+ {
+ // Not a valid \uXXXX sequence
+ p -= i;
+ return 'u';
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ p++;
+ Lretc:
+ return c;
+ }
+
+/* ==================== optimizer ======================= */
+
+ void optimize()
+ { ubyte[] prog;
+
+ debug(regexp) printf("RegExp.optimize()\n");
+ prog = buf.toBytes();
+ for (size_t i = 0; 1;)
+ {
+ //printf("\tprog[%d] = %d, %d\n", i, prog[i], REstring);
+ switch (prog[i])
+ {
+ case REend:
+ case REanychar:
+ case REanystar:
+ case REbackref:
+ case REeol:
+ case REchar:
+ case REichar:
+ case REdchar:
+ case REidchar:
+ case REstring:
+ case REistring:
+ case REtestbit:
+ case REbit:
+ case REnotbit:
+ case RErange:
+ case REnotrange:
+ case REwordboundary:
+ case REnotwordboundary:
+ case REdigit:
+ case REnotdigit:
+ case REspace:
+ case REnotspace:
+ case REword:
+ case REnotword:
+ return;
+
+ case REbol:
+ i++;
+ continue;
+
+ case REor:
+ case REnm:
+ case REnmq:
+ case REparen:
+ case REgoto:
+ {
+ auto bitbuf = new OutBuffer;
+ auto r = new Range(bitbuf);
+ auto offset = i;
+ if (starrchars(r, prog[i .. prog.length]))
+ {
+ debug(regexp) printf("\tfilter built\n");
+ buf.spread(offset, 1 + 4 + r.maxb);
+ buf.data[offset] = REtestbit;
+ (cast(ushort *)&buf.data[offset + 1])[0] = cast(ushort)r.maxc;
+ (cast(ushort *)&buf.data[offset + 1])[1] = cast(ushort)r.maxb;
+ i = offset + 1 + 4;
+ buf.data[i .. i + r.maxb] = r.base[0 .. r.maxb];
+ }
+ return;
+ }
+ default:
+ assert(0);
+ }
+ }
+ }
+
+/////////////////////////////////////////
+// OR the leading character bits into r.
+// Limit the character range from 0..7F,
+// trymatch() will allow through anything over maxc.
+// Return 1 if success, 0 if we can't build a filter or
+// if there is no point to one.
+
+ int starrchars(Range r, const(ubyte)[] prog)
+ { rchar c;
+ uint maxc;
+ size_t maxb;
+ size_t len;
+ uint b;
+ uint n;
+ uint m;
+ const(ubyte)* pop;
+
+ //printf("RegExp.starrchars(prog = %p, progend = %p)\n", prog, progend);
+ for (size_t i = 0; i < prog.length;)
+ {
+ switch (prog[i])
+ {
+ case REchar:
+ c = prog[i + 1];
+ if (c <= 0x7F)
+ r.setbit2(c);
+ return 1;
+
+ case REichar:
+ c = prog[i + 1];
+ if (c <= 0x7F)
+ { r.setbit2(c);
+ r.setbit2(std.ascii.toLower(cast(rchar)c));
+ }
+ return 1;
+
+ case REdchar:
+ case REidchar:
+ return 1;
+
+ case REanychar:
+ return 0; // no point
+
+ case REstring:
+ len = *cast(size_t *)&prog[i + 1];
+ assert(len);
+ c = *cast(rchar *)&prog[i + 1 + size_t.sizeof];
+ debug(regexp) printf("\tREstring %d, '%c'\n", len, c);
+ if (c <= 0x7F)
+ r.setbit2(c);
+ return 1;
+
+ case REistring:
+ len = *cast(size_t *)&prog[i + 1];
+ assert(len);
+ c = *cast(rchar *)&prog[i + 1 + size_t.sizeof];
+ debug(regexp) printf("\tREistring %d, '%c'\n", len, c);
+ if (c <= 0x7F)
+ { r.setbit2(std.ascii.toUpper(cast(rchar)c));
+ r.setbit2(std.ascii.toLower(cast(rchar)c));
+ }
+ return 1;
+
+ case REtestbit:
+ case REbit:
+ maxc = (cast(ushort *)&prog[i + 1])[0];
+ maxb = (cast(ushort *)&prog[i + 1])[1];
+ if (maxc <= 0x7F)
+ r.setbitmax(maxc);
+ else
+ maxb = r.maxb;
+ for (b = 0; b < maxb; b++)
+ r.base[b] |= prog[i + 1 + 4 + b];
+ return 1;
+
+ case REnotbit:
+ maxc = (cast(ushort *)&prog[i + 1])[0];
+ maxb = (cast(ushort *)&prog[i + 1])[1];
+ if (maxc <= 0x7F)
+ r.setbitmax(maxc);
+ else
+ maxb = r.maxb;
+ for (b = 0; b < maxb; b++)
+ r.base[b] |= ~prog[i + 1 + 4 + b];
+ return 1;
+
+ case REbol:
+ case REeol:
+ return 0;
+
+ case REor:
+ len = (cast(uint *)&prog[i + 1])[0];
+ return starrchars(r, prog[i + 1 + uint.sizeof .. prog.length]) &&
+ starrchars(r, prog[i + 1 + uint.sizeof + len .. prog.length]);
+
+ case REgoto:
+ len = (cast(uint *)&prog[i + 1])[0];
+ i += 1 + uint.sizeof + len;
+ break;
+
+ case REanystar:
+ return 0;
+
+ case REnm:
+ case REnmq:
+ // len, n, m, ()
+ len = (cast(uint *)&prog[i + 1])[0];
+ n = (cast(uint *)&prog[i + 1])[1];
+ m = (cast(uint *)&prog[i + 1])[2];
+ pop = &prog[i + 1 + uint.sizeof * 3];
+ if (!starrchars(r, pop[0 .. len]))
+ return 0;
+ if (n)
+ return 1;
+ i += 1 + uint.sizeof * 3 + len;
+ break;
+
+ case REparen:
+ // len, ()
+ len = (cast(uint *)&prog[i + 1])[0];
+ n = (cast(uint *)&prog[i + 1])[1];
+ pop = &prog[0] + i + 1 + uint.sizeof * 2;
+ return starrchars(r, pop[0 .. len]);
+
+ case REend:
+ return 0;
+
+ case REwordboundary:
+ case REnotwordboundary:
+ return 0;
+
+ case REdigit:
+ r.setbitmax('9');
+ for (c = '0'; c <= '9'; c++)
+ r.bits[c] = 1;
+ return 1;
+
+ case REnotdigit:
+ r.setbitmax(0x7F);
+ for (c = 0; c <= '0'; c++)
+ r.bits[c] = 1;
+ for (c = '9' + 1; c <= r.maxc; c++)
+ r.bits[c] = 1;
+ return 1;
+
+ case REspace:
+ r.setbitmax(0x7F);
+ for (c = 0; c <= r.maxc; c++)
+ if (isWhite(c))
+ r.bits[c] = 1;
+ return 1;
+
+ case REnotspace:
+ r.setbitmax(0x7F);
+ for (c = 0; c <= r.maxc; c++)
+ if (!isWhite(c))
+ r.bits[c] = 1;
+ return 1;
+
+ case REword:
+ r.setbitmax(0x7F);
+ for (c = 0; c <= r.maxc; c++)
+ if (isword(cast(rchar)c))
+ r.bits[c] = 1;
+ return 1;
+
+ case REnotword:
+ r.setbitmax(0x7F);
+ for (c = 0; c <= r.maxc; c++)
+ if (!isword(cast(rchar)c))
+ r.bits[c] = 1;
+ return 1;
+
+ case REbackref:
+ return 0;
+
+ default:
+ assert(0);
+ }
+ }
+ return 1;
+ }
+
+/* ==================== replace ======================= */
+
+/***********************
+ * After a match is found with test(), this function
+ * will take the match results and, using the format
+ * string, generate and return a new string.
+ */
+
+ public string replace(string format)
+ {
+ return replace3(format, input, pmatch[0 .. re_nsub + 1]);
+ }
+
+// Static version that doesn't require a RegExp object to be created
+
+ public static string replace3(string format, string input, regmatch_t[] pmatch)
+ {
+ string result;
+ size_t c2;
+ sizediff_t rm_so, rm_eo, i;
+
+// printf("replace3(format = '%.*s', input = '%.*s')\n", format.length, format.ptr, input.length, input.ptr);
+ result.length = format.length;
+ result.length = 0;
+ for (size_t f = 0; f < format.length; f++)
+ {
+ char c = format[f];
+ L1:
+ if (c != '$')
+ {
+ result ~= c;
+ continue;
+ }
+ ++f;
+ if (f == format.length)
+ {
+ result ~= '$';
+ break;
+ }
+ c = format[f];
+ switch (c)
+ {
+ case '&':
+ rm_so = pmatch[0].rm_so;
+ rm_eo = pmatch[0].rm_eo;
+ goto Lstring;
+
+ case '`':
+ rm_so = 0;
+ rm_eo = pmatch[0].rm_so;
+ goto Lstring;
+
+ case '\'':
+ rm_so = pmatch[0].rm_eo;
+ rm_eo = input.length;
+ goto Lstring;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ i = c - '0';
+ if (f + 1 == format.length)
+ {
+ if (i == 0)
+ {
+ result ~= '$';
+ result ~= c;
+ continue;
+ }
+ }
+ else
+ {
+ c2 = format[f + 1];
+ if (c2 >= '0' && c2 <= '9')
+ {
+ i = (c - '0') * 10 + (c2 - '0');
+ f++;
+ }
+ if (i == 0)
+ {
+ result ~= '$';
+ result ~= c;
+ c = cast(char)c2;
+ goto L1;
+ }
+ }
+
+ if (i < pmatch.length)
+ { rm_so = pmatch[i].rm_so;
+ rm_eo = pmatch[i].rm_eo;
+ goto Lstring;
+ }
+ break;
+
+ Lstring:
+ if (rm_so != rm_eo)
+ result ~= input[rm_so .. rm_eo];
+ break;
+
+ default:
+ result ~= '$';
+ result ~= c;
+ break;
+ }
+ }
+ return result;
+ }
+
+/************************************
+ * Like replace(char[] format), but uses old style formatting:
+ <table border=1 cellspacing=0 cellpadding=5>
+ <th>Format
+ <th>Description
+ <tr>
+ <td><b>&</b>
+ <td>replace with the match
+ </tr>
+ <tr>
+ <td><b>\</b><i>n</i>
+ <td>replace with the <i>n</i>th parenthesized match, <i>n</i> is 1..9
+ </tr>
+ <tr>
+ <td><b>\</b><i>c</i>
+ <td>replace with char <i>c</i>.
+ </tr>
+ </table>
+*/
+
+ public string replaceOld(string format)
+ {
+ string result;
+
+//printf("replace: this = %p so = %d, eo = %d\n", this, pmatch[0].rm_so, pmatch[0].rm_eo);
+//printf("3input = '%.*s'\n", input.length, input.ptr);
+ result.length = format.length;
+ result.length = 0;
+ for (size_t i; i < format.length; i++)
+ {
+ char c = format[i];
+ switch (c)
+ {
+ case '&':
+ {
+ auto sss = input[pmatch[0].rm_so .. pmatch[0].rm_eo];
+ //printf("match = '%.*s'\n", sss.length, sss.ptr);
+ result ~= sss;
+ }
+ break;
+
+ case '\\':
+ if (i + 1 < format.length)
+ {
+ c = format[++i];
+ if (c >= '1' && c <= '9')
+ { uint j;
+
+ j = c - '0';
+ if (j <= re_nsub && pmatch[j].rm_so != pmatch[j].rm_eo)
+ result ~= input[pmatch[j].rm_so .. pmatch[j].rm_eo];
+ break;
+ }
+ }
+ result ~= c;
+ break;
+
+ default:
+ result ~= c;
+ break;
+ }
+ }
+ return result;
+ }
+
+}
+
+unittest
+{ // Created and placed in public domain by Don Clugston
+
+ auto m = search("aBC r s", `bc\x20r[\40]s`, "i");
+ assert(m.pre=="a");
+ assert(m[0]=="BC r s");
+ auto m2 = search("7xxyxxx", `^\d([a-z]{2})\D\1`);
+ assert(m2[0]=="7xxyxx");
+ // Just check the parsing.
+ auto m3 = search("dcbxx", `ca|b[\d\]\D\s\S\w-\W]`);
+ auto m4 = search("xy", `[^\ca-\xFa\r\n\b\f\t\v\0123]{2,485}$`);
+ auto m5 = search("xxx", `^^\r\n\b{13,}\f{4}\t\v\u02aF3a\w\W`);
+ auto m6 = search("xxy", `.*y`);
+ assert(m6[0]=="xxy");
+ auto m7 = search("QWDEfGH", "(ca|b|defg)+", "i");
+ assert(m7[0]=="DEfG");
+ auto m8 = search("dcbxx", `a?\B\s\S`);
+ auto m9 = search("dcbxx", `[-w]`);
+ auto m10 = search("dcbsfd", `aB[c-fW]dB|\d|\D|\u012356|\w|\W|\s|\S`, "i");
+ auto m11 = search("dcbsfd", `[]a-]`);
+ m.replaceOld(`a&b\1c`);
+ m.replace(`a$&b$'$1c`);
+}
+
+// Andrei
+//------------------------------------------------------------------------------
+
+struct Pattern(Char)
+{
+ immutable(Char)[] pattern;
+
+ this(immutable(Char)[] pattern)
+ {
+ this.pattern = pattern;
+ }
+}
+
+Pattern!(Char) pattern(Char)(immutable(Char)[] pat)
+{
+ return typeof(return)(pat);
+}
+
+struct Splitter(Range)
+{
+ Range _input;
+ size_t _chunkLength;
+ RegExp _rx;
+
+ private Range search()
+ {
+ //rx = undead.regexp.search(_input, "(" ~ _separator.pattern ~ ")");
+ auto i = undead.regexp.find(cast(string) _input, _rx);
+ return _input[i >= 0 ? i : _input.length .. _input.length];
+ }
+
+ private void advance()
+ {
+ //writeln("(" ~ _separator.pattern ~ ")");
+ //writeln(_input);
+ //assert(_rx[0].length > 0);
+ _chunkLength += _rx[0].length;
+ }
+
+ this(Range input, Pattern!(char) separator)
+ {
+ _input = input;
+ _rx = RegExp(separator.pattern);
+ _chunkLength = _input.length - search().length;
+ }
+
+ ref auto opSlice()
+ {
+ return this;
+ }
+
+ @property Range front()
+ {
+ return _input[0 .. _chunkLength];
+ }
+
+ @property bool empty()
+ {
+ return _input.empty;
+ }
+
+ void popFront()
+ {
+ if (_chunkLength == _input.length)
+ {
+ _input = _input[_chunkLength .. _input.length];
+ return;
+ }
+ advance();
+ _input = _input[_chunkLength .. _input.length];
+ _chunkLength = _input.length - search().length;
+ }
+}
+
+Splitter!(Range) splitter(Range)(Range r, Pattern!(char) pat)
+{
+ static assert(is(Unqual!(typeof(Range.init[0])) == char),
+ Unqual!(typeof(Range.init[0])).stringof);
+ return typeof(return)(cast(string) r, pat);
+}
+
+unittest
+{
+ auto s1 = ", abc, de, fg, hi, ";
+ auto sp2 = splitter(s1, pattern(", *"));
+ //foreach (e; sp2) writeln("[", e, "]");
+ assert(equal(sp2, ["", "abc", "de", "fg", "hi"][]));
+}
+
+unittest
+{
+ auto str= "foo";
+ string[] re_strs= [
+ r"^(h|a|)fo[oas]$",
+ r"^(a|b|)fo[oas]$",
+ r"^(a|)foo$",
+ r"(a|)foo",
+ r"^(h|)foo$",
+ r"(h|)foo",
+ r"(h|a|)fo[oas]",
+ r"^(a|b|)fo[o]$",
+ r"[abf][ops](o|oo|)(h|a|)",
+ r"(h|)[abf][ops](o|oo|)",
+ r"(c|)[abf][ops](o|oo|)"
+ ];
+
+ foreach (re_str; re_strs) {
+ auto re= new RegExp(re_str);
+ auto matches= cast(bool)re.test(str);
+ assert(matches);
+ //writefln("'%s' matches '%s' ? %s", str, re_str, matches);
+ }
+
+ for (char c='a'; c<='z'; ++c) {
+ auto re_str= "("~c~"|)foo";
+ auto re= new RegExp(re_str);
+ auto matches= cast(bool)re.test(str);
+ assert(matches);
+ //writefln("'%s' matches '%s' ? %s", str, re_str, matches);
+ }
+}
diff --git a/src/undead/socketstream.d b/src/undead/socketstream.d
new file mode 100644
index 0000000..33d0515
--- /dev/null
+++ b/src/undead/socketstream.d
@@ -0,0 +1,147 @@
+// 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.)
+ *
+ * $(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
+ */
+module undead.socketstream;
+
+private import undead.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/src/undead/stream.d b/src/undead/stream.d
new file mode 100644
index 0000000..e31b381
--- /dev/null
+++ b/src/undead/stream.d
@@ -0,0 +1,3071 @@
+// Written in the D programming language
+
+/**
+ * $(RED Deprecated: This module is considered out-dated and not up to Phobos'
+ * current standards.)
+ *
+ * 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.
+ */
+module undead.stream;
+
+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;
+ import undead.internal.file;
+ import undead.doformat;
+}
+
+/// 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 = undead.internal.file.deleteme ~ "-issue3363.txt";
+ auto w = File(fileName, "w");
+ scope (exit) remove(fileName.ptr);
+ w.write("one two three");
+ w.close();
+ auto r = 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 {
+ import std.internal.cstring : tempCString;
+
+ File file = new File;
+ int i = 666;
+ auto stream_file = undead.internal.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();
+ remove(stream_file.tempCString());
+ }
+}
+
+/***
+ * 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 {
+ import std.internal.cstring : tempCString;
+
+ BufferedFile file = new BufferedFile;
+ int i = 666;
+ auto stream_file = undead.internal.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();
+ remove(stream_file.tempCString());
+ }
+
+}
+
+/// 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 = undead.internal.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/win32.mak b/win32.mak
new file mode 100644
index 0000000..f9a4ad1
--- /dev/null
+++ b/win32.mak
@@ -0,0 +1,55 @@
+#_ win32.mak
+# Build win32 version of undead
+# Needs Digital Mars D compiler to build, available free from:
+# http://www.digitalmars.com/d/
+
+DMD=dmd
+DEL=del
+S=src\undead
+O=obj
+B=bin
+
+TARGET=undead
+
+DFLAGS=-g -Isrc/
+LFLAGS=-L/map/co
+#DFLAGS=
+#LFLAGS=
+
+.d.obj :
+ $(DMD) -c $(DFLAGS) $*
+
+SRC= $S\bitarray.d $S\regexp.d $S\datebase.d $S\date.d $S\dateparse.d \
+ $S\cstream.d $S\stream.d $S\socketstream.d $S\doformat.d
+
+
+SOURCE= $(SRC) win32.mak posix.mak LICENSE README.md dub.json
+
+all: $B\$(TARGET).lib
+
+#################################################
+
+$B\$(TARGET).lib : $(SRC)
+ $(DMD) -lib -of$B\$(TARGET).lib $(SRC) $(DFLAGS)
+
+
+unittest :
+ $(DMD) -unittest -main -cov -of$O\unittest.exe $(SRC) $(DFLAGS)
+ $O\unittest.exe
+
+
+clean:
+ $(DEL) $O\unittest.exe *.lst
+
+
+tolf:
+ tolf $(SOURCE)
+
+
+detab:
+ detab $(SRC)
+
+
+zip: detab tolf $(SOURCE)
+ $(DEL) undead.zip
+ zip32 undead $(SOURCE)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/libundead.git
More information about the debian-med-commit
mailing list