From 713ef9ffdfb01caac54cdef6e449819295c3bd8c Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 14 Sep 2014 14:28:13 +0400 Subject: [PATCH 01/46] std.internal.math.summation added std.internal.math.summation: tabs std.algorithm: sum (Kahan and Pairswise) update *.mak update std.interanl.math.summation update std.numeric: added fsum std.interanl.math.summation update std.interanl.math.summation update std.interanl.math.summation update std.interanl.math.summation add local fabs std.interanl.math.summation Docs update minor update tabs minor ddoc update minor ddoc update remove reverse summation Full redesign std.internal.math.summation: privacy changes std.internal.math.summation: privacy changes fabs update opertor overlaoding for Summator opertor overlaoding for Summator update opertor overlaoding unittest added update Summator remove blank lines update Summator unittests add unittest for Complex std.internal.scopebuffer: added inout for slices update unittest removed no-op cast add opAssign and const for operators remove reset (use = 0) added extendTo template update fsum for Complex remove static if for __ctfe fsum update minor add CTFEable param added = void minor update update location of Summator _ update summation.d --- posix.mak | 2 +- std/algorithm.d | 37 +- std/internal/math/summation.d | 879 ++++++++++++++++++++++++++++++++++ std/internal/scopebuffer.d | 22 +- std/numeric.d | 240 +++++++++- win32.mak | 3 +- win64.mak | 3 +- 7 files changed, 1149 insertions(+), 37 deletions(-) create mode 100644 std/internal/math/summation.d diff --git a/posix.mak b/posix.mak index 60bee1cc59e..27dbd8fb1e4 100644 --- a/posix.mak +++ b/posix.mak @@ -210,7 +210,7 @@ time wcharh) EXTRA_MODULES += $(EXTRA_DOCUMENTABLES) $(addprefix \ std/internal/digest/, sha_SSSE3 ) $(addprefix \ std/internal/math/, biguintcore biguintnoasm biguintx86 \ - gammafunction errorfunction) $(addprefix std/internal/, \ + gammafunction errorfunction summation) $(addprefix std/internal/, \ cstring processinit unicode_tables scopebuffer\ unicode_comp unicode_decomp unicode_grapheme unicode_norm) diff --git a/std/algorithm.d b/std/algorithm.d index 4a9da07fa33..7a00f1a587b 100644 --- a/std/algorithm.d +++ b/std/algorithm.d @@ -1169,9 +1169,15 @@ if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front))) static if (isFloatingPoint!E) { static if (hasLength!R && hasSlicing!R) - return seed + sumPairwise!E(r); + { + import std.internal.math.summation : sumPairwise; + return seed + sumPairwise!(R, E)(r); + } else - return sumKahan!E(seed, r); + { + import std.internal.math.summation : sumKahan; + return sumKahan!(R, E)(r, seed); + } } else { @@ -1179,33 +1185,6 @@ if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front))) } } -// Pairwise summation http://en.wikipedia.org/wiki/Pairwise_summation -private auto sumPairwise(Result, R)(R r) -{ - static assert (isFloatingPoint!Result); - switch (r.length) - { - case 0: return cast(Result) 0; - case 1: return cast(Result) r.front; - case 2: return cast(Result) r[0] + cast(Result) r[1]; - default: return sumPairwise!Result(r[0 .. $ / 2]) + sumPairwise!Result(r[$ / 2 .. $]); - } -} - -// Kahan algo http://en.wikipedia.org/wiki/Kahan_summation_algorithm -private auto sumKahan(Result, R)(Result result, R r) -{ - static assert (isFloatingPoint!Result && isMutable!Result); - Result c = 0; - for (; !r.empty; r.popFront()) - { - auto y = r.front - c; - auto t = result + y; - c = (t - result) - y; - result = t; - } - return result; -} /// Ditto @safe pure nothrow unittest diff --git a/std/internal/math/summation.d b/std/internal/math/summation.d new file mode 100644 index 00000000000..5c76508184f --- /dev/null +++ b/std/internal/math/summation.d @@ -0,0 +1,879 @@ +/* + * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Ilya Yaroshenko + * Source: $(PHOBOSSRC std/internal/math/_summation.d) + */ +module std.internal.math.summation; + +import std.traits : + Unqual, + isIterable, + isMutable, + isImplicitlyConvertible, + ForeachType, + isFloatingPoint; + +private template isComplex(C) +{ + import std.complex : Complex; + enum isComplex = is(C : Complex!F, F); +} + +private F fabs(F)(F f) //+-0, +-NaN, +-inf no matter +{ + if(__ctfe) + { + return f < 0 ? -f : f; + } + else + { + version(LDC) + { + import ldc.intrinsics : llvm_fabs; + return llvm_fabs(f); + } + else + { + import core.stdc.tgmath : fabs; + return fabs(f); + } + } +} + +/** +Naive summation algorithm. +*/ +F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +if( + isMutable!F && + isIterable!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) +) +{ + foreach(F x; r) + { + s += x; + } + return s; +} + +/** +$(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. +*/ +F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r) +if( + isMutable!F && + isIterable!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) +) +{ + import std.range : hasLength, hasSlicing; + static assert(hasLength!Range && hasSlicing!Range); + switch (r.length) + { + case 0: return F(0); + case 1: return cast(F)r[0]; + case 2: return cast(F)(r[0] + cast(F)r[1]); + default: auto n = r.length/2; return cast(F)(sumPairwise!(Range, F)(r[0..n]) + sumPairwise!(Range, F)(r[n..$])); + } +} + +/** +$(LUCKY Kahan summation) algorithm. +*/ +/** +--------------------- +s := x[1] +c := 0 +FOR k := 2 TO n DO + y := x[k] - c + t := s + y + c := (t - s) - y + s := t +END DO +--------------------- +*/ +F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) +if( + isMutable!F && + isIterable!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) +) +{ + F c = 0.0; + F y; // do not declare in the loop (algo can be used for matrixes and etc) + F t; // ditto + foreach(F x; r) + { + y = x - c; + t = s + y; + c = t - s; + c -= y; + s = t; + } + return s; +} + +/** +$(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). +*/ +/** +--------------------- +s := x[1] +c := 0 +FOR i := 2 TO n DO + t := s + x[i] + IF ABS(s) >= ABS(x[i]) THEN + c := c + ((s-t)+x[i]) + ELSE + c := c + ((x[i]-t)+s) + END IF + s := t +END DO +s := s + c +--------------------- +*/ +F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) +if( + isMutable!F && + isIterable!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && + (isFloatingPoint!F || isComplex!F) +) +{ + F c = 0.0; + static if(isFloatingPoint!F) + { + foreach(F x; r) + { + F t = s + x; + if(s.fabs >= x.fabs) + c += (s-t)+x; + else + c += (x-t)+s; + s = t; + } + } + else + { + foreach(F x; r) + { + F t = s + x; + if(s.re.fabs < x.re.fabs) + { + auto t_re = s.re; + s.re = x.re; + x.re = t_re; + } + if(s.im.fabs < x.im.fabs) + { + auto t_im = s.im; + s.im = x.im; + x.im = t_im; + } + c += (s-t)+x; + s = t; + } + } + return s + c; +} + +/** +$(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). +*/ +/** +--------------------- +s := 0 ; cs := 0 ; ccs := 0 +FOR j := 1 TO n DO + t := s + x[i] + IF ABS(s) >= ABS(x[i]) THEN + c := (s-t) + x[i] + ELSE + c := (x[i]-t) + s + END IF + s := t + t := cs + c + IF ABS(cs) >= ABS(c) THEN + cc := (cs-t) + c + ELSE + cc := (c-t) + cs + END IF + cs := t + ccs := ccs + cc +END FOR +RETURN s+cs+ccs +--------------------- +*/ +F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) +if( + isMutable!F && + isIterable!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && + (isFloatingPoint!F || isComplex!F) +) +{ + F cs = 0.0; + F ccs = 0.0; + static if(isFloatingPoint!F) + { + foreach(F x; r) + { + F t = s + x; + F c = void; + if(s.fabs >= x.fabs) + c = (s-t)+x; + else + c = (x-t)+s; + s = t; + t = cs + c; + if(cs.fabs >= c.fabs) + ccs += (cs-t)+c; + else + ccs += (c-t)+cs; + cs = t; + } + } + else + { + foreach(F x; r) + { + F t = s + x; + if(s.re.fabs < x.re.fabs) + { + auto t_re = s.re; + s.re = x.re; + x.re = t_re; + } + if(s.im.fabs < x.im.fabs) + { + auto t_im = s.im; + s.im = x.im; + x.im = t_im; + } + F c = (s-t)+x; + s = t; + if(cs.re.fabs < c.re.fabs) + { + auto t_re = cs.re; + cs.re = c.re; + c.re = t_re; + } + if(cs.im.fabs < c.im.fabs) + { + auto t_im = cs.im; + cs.im = c.im; + c.im = t_im; + } + ccs += (cs-t)+c; + cs = t; + } + } + return s+cs+ccs; // no rounding in between +} + +unittest +{ + import std.typetuple; + foreach(I; TypeTuple!(byte, uint, long)) + { + I[] ar = [1, 2, 3, 4]; + I r = 10; + assert(r == ar.sumNaive); + assert(r == ar.sumPairwise); + } +} + +unittest +{ + import std.typetuple; + foreach(F; TypeTuple!(float, double, real)) + { + F[] ar = [1, 2, 3, 4]; + F r = 10; + assert(r == ar.sumNaive); + assert(r == ar.sumPairwise); + assert(r == ar.sumKahan); + assert(r == ar.sumKBN); + assert(r == ar.sumKB2); + } +} + +unittest +{ + import std.complex; + Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; + Complex!double r = complex(10, 14); + assert(r == ar.sumNaive); + assert(r == ar.sumPairwise); + assert(r == ar.sumKahan); + assert(r == ar.sumKBN); + assert(r == ar.sumKB2); +} + +//BUG: DMD 2.066 Segmentation fault (core dumped) +//unittest +//{ +// import core.simd; +// static if(__traits(compiles, double2.init + double2.init)) +// { +// double2[] ar = [double2([1.0, 2]), double2([2, 3]), double2([3, 4]), double2([4, 6])]; +// assert(ar.sumNaive.array == double2([10, 14]).array); +// assert(ar.sumPairwise.array == double2([10, 14]).array); +// assert(ar.sumKahan.array == double2([10, 14]).array); +// } +//} + +unittest +{ + import std.algorithm : map; + auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); + double r = 20000; + assert(r != ar.sumNaive); + assert(r != ar.sumPairwise); + assert(r != ar.sumKahan); + assert(r == ar.sumKBN); + assert(r == ar.sumKB2); +} + +/** +Handler for full precise summation with $(D put) primitive. +The current implementation re-establish special +value semantics across iterations (i.e. handling -inf + inf). +*/ +/* +Precise summation function as msum() by Raymond Hettinger in +, +enhanced with the exact partials sum and roundoff from Mark +Dickinson's post at . +See those links for more details, proofs and other references. + +Note: IEEE 754R floating point semantics are assumed. +*/ +struct Summator(F, bool CTFEable = false) +if(isFloatingPoint!F && is(Unqual!F == F)) +{ + import std.internal.scopebuffer; + static if(CTFEable) import std.conv; +private: + enum F M = (cast(F)(2)) ^^ (F.max_exp - 1); + + static if(CTFEable) + { + static assert(0, "CTFEable Summator not implemented."); + } + else + { + F[32] scopeBufferArray = void; + ScopeBuffer!F partials; + } + + //sum for NaN and infinity. + F s; + //Overflow Degree. Count of 2^^F.max_exp minus count of -(2^^F.max_exp) + sizediff_t o; + + debug(numeric) + void partialsDebug() const + { + foreach(y; partials[]) + { + assert(y); + assert(y.isFinite); + } + //TODO: Add NonOverlaping check to std.math + import std.algorithm : isSorted, map; + assert(partials[].map!fabs.isSorted); + } + + /** + Compute the sum of a list of nonoverlapping floats. + On input, partials is a list of nonzero, nonspecial, + nonoverlapping floats, strictly increasing in magnitude, but + possibly not all having the same sign. + On output, the sum of partials gives the error in the returned + result, which is correctly rounded (using the round-half-to-even + rule). + Two floating point values x and y are non-overlapping if the least significant nonzero + bit of x is more significant than the most significant nonzero bit of y, or vice-versa. + */ + static F partialsReduce(F s, in F[] partials) + in + { + debug(numeric) assert(!partials.length || s.isFinite); + } + body + { + bool _break = void; + foreach_reverse(i, y; partials) + { + s = partialsReducePred(s, y, i ? partials[i-1] : 0, _break); + if(_break) + break; + debug(numeric) assert(s.isFinite); + } + return s; + } + + static F partialsReducePred(F s, F y, F z, out bool _break) + out(result) + { + debug(numeric) assert(result.isFinite); + } + body + { + F x = s; + s = x + y; + F l = y - (s - x); + debug(numeric) + { + assert(x.isFinite); + assert(y.isFinite); + assert(s.isFinite); + assert(fabs(y) < fabs(x)); + } + if(l) + { + //Make half-even rounding work across multiple partials. + //Needed so that sum([1e-16, 1, 1e16]) will round-up the last + //digit to two instead of down to zero (the 1e-16 makes the 1 + //slightly closer to two). Can guarantee commutativity. + if(z && !signbit(l * z)) + { + l *= 2; + x = s + l; + if (l == x - s) + s = x; + } + _break = true; + } + return s; + } + + //Returns corresponding infinity if is overflow and 0 otherwise. + F overflow() const + { + if(o == 0) + return 0; + if(partials.length && (o == -1 || o == 1) && signbit(o * partials[$-1])) + { + // problem case: decide whether result is representable + F x = o * M; + F y = partials[$-1] / 2; + F h = x + y; + F l = (y - (h - x)) * 2; + y = h * 2; + if(!y.isInfinity || partials.length > 1 && !signbit(l * partials[$-2]) && (h + l) - h == l) + return 0; + } + return F.infinity * o; + } + +public: + + /// + this(F f) + { + partials = scopeBuffer(scopeBufferArray); + s = 0; + o = 0; + if(f) put(f); + } + + /// + @disable this(); + + // free ScopeBuffer + ~this() + { + partials.free; + } + + // copy ScopeBuffer if necessary + this(this) + { + auto a = partials[]; + if(scopeBufferArray.ptr !is a.ptr) + { + partials = scopeBuffer(scopeBufferArray); + partials.put(a); + } + } + + ///Adds $(D x) to internal partial sums. + void put(F x) + { + if(x.isFinite) + { + size_t i; + foreach(y; partials[]) + { + F h = x + y; + if(h.isInfinity) + { + if(fabs(x) < fabs(y)) + { + F t = x; x = y; y = t; + } + //h == -F.infinity + if(h.signbit) + { + x += M; + x += M; + o--; + } + //h == +F.infinity + else + { + x -= M; + x -= M; + o++; + } + debug(numeric) assert(x.isFinite); + h = x + y; + } + debug(numeric) assert(h.isFinite); + F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); + debug(numeric) assert(l.isFinite); + if(l) + { + partials[i++] = l; + } + x = h; + } + partials.length = i; + if(x) + { + partials.put(x); + } + } + else + { + s += x; + } + } + + /** + Adds $(D x) to internal partial sums. + + */ + void unsafePut(F x) + { + size_t i; + foreach(y; partials[]) + { + F h = x + y; + debug(numeric) assert(h.isFinite); + F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); + debug(numeric) assert(l.isFinite); + if(l) + { + partials[i++] = l; + } + x = h; + } + partials.length = i; + if(x) + { + partials.put(x); + } + } + + /** + Returns the value of the sum, rounded to the nearest representable + floating-point number using the round-half-to-even rule. + */ + F sum() const + { + debug(numeric) partialsDebug; + + if(s) + return s; + auto parts = partials[]; + F y = 0; + //pick last + if(parts.length) + { + y = parts[$-1]; + parts = parts[0..$-1]; + } + if(o) + { + immutable F of = o; + if(y && (o == -1 || o == 1) && signbit(of * y)) + { + // problem case: decide whether result is representable + y /= 2; + F x = of * M; + immutable F h = x + y; + F l = (y - (h - x)) * 2; + y = h * 2; + if(y.isInfinity) + { + // overflow, except in edge case... + x = h + l; + y = parts.length && x - h == l && !signbit(l*parts[$-1]) ? + x * 2 : + F.infinity * of; + parts = null; + } + else if(l) + { + bool _break; + y = partialsReducePred(y, l, parts.length ? parts[$-1] : 0, _break); + if(_break) + parts = null; + } + } + else + { + y = F.infinity * of; + parts = null; + } + } + return partialsReduce(y, parts); + } + + /** + */ + F partialsSum() const + { + debug(numeric) partialsDebug; + auto parts = partials[]; + F y = 0; + //pick last + if(parts.length) + { + y = parts[$-1]; + parts = parts[0..$-1]; + } + return partialsReduce(y, parts); + } + + /// + @property F nonFinitySum() const + { + return s; + } + + /// + @property sizediff_t overflowDegree() const + { + return o; + } + + /// + void resetNonPartials() + { + s = 0; + o = 0; + } + + ///Returns $(D Summator) with extended internal partial sums. + Summator!(Unqual!E) extendTo(E)() if( + isFloatingPoint!E && + E.max_exp >= F.max_exp && + E.mant_dig >= F.mant_dig + ) + { + static if(is(Unqual!E == F)) + return this; + else + { + typeof(return) ret = void; + ret.s = s; + ret.o = o; + ret.partials = scopeBuffer(ret.scopeBufferArray); + foreach(p; partials[]) + { + ret.partials.put(p); + } + enum exp_diff = E.max_exp - F.max_exp; + static if(exp_diff) + { + if(ret.o) + { + //enum shift = 2u ^^ exp_diff; + immutable f = ret.o >> exp_diff; + immutable t = cast(int)(ret.o - (f << exp_diff)); + ret.o = f; + ret.put(E(2) ^^ t); + } + } + } + } + + /// + unittest + { + import std.math; + float M = 2.0f ^^ (float.max_exp-1); + double N = 2.0 ^^ (float.max_exp-1); + auto s = Summator(0.0f); //float summator + s += M; + s += M; + auto e = s.extendTo!double; + + assert(M+M == s.sum); + assert(M+M == float.infinity); + + assert(N+N == e.sum); + assert(N+N != double.infinity); + } + + /** + $(D cast(F)) operator overlaoding. Returns $(D cast(T)sum()). + See also: $(D extendTo) + */ + T opCast(T)() if(Unqual!T == F) + { + return sum(); + } + + ///The assignment operator $(D =) overlaoding. + void opAssign(F rhs) + { + partials.length = 0; + s = 0; + o = 0; + if(rhs) put(rhs); + } + + /// += and -= operator overlaoding. + void opOpAssign(string op : "+")(F f) + { + put(f); + } + + ///ditto + void opOpAssign(string op : "+")(ref const Summator rhs) + { + s += rhs.s; + o += rhs.o; + foreach(f; rhs.partials[]) + put(f); + } + + ///ditto + void opOpAssign(string op : "-")(F f) + { + put(-f); + } + + ///ditto + void opOpAssign(string op : "-")(ref const Summator rhs) + { + s -= rhs.s; + o -= rhs.o; + foreach(f; rhs.partials[]) + put(-f); + } + + /// + unittest { + import std.math, std.algorithm, std.range; + auto r1 = iota( 1, 501 ).map!(a => (-1.0)^^a/a); + auto r2 = iota(501, 1001).map!(a => (-1.0)^^a/a); + Summator!double s1 = 0.69264743055982025, s2 = 0; + foreach(e; r1) s1 += e; + foreach(e; r2) s2 -= e; + s1 -= s2; + assert(s1.sum == 0); + } + + ///Returns $(D true) if current sum is a NaN. + bool isNaN() const + { + if(s.isNaN) + return true; + if(s) + return (s + overflow).isNaN; + return false; + } + + ///Returns $(D true) if current sum is finite (not infinite or NaN). + bool isFinite() const + { + if(s) + return false; + return !overflow; + } + + ///Returns $(D true) if current sum is ±∞. + bool isInfinity() const + { + if(s.isNaN) + return false; + return (s + overflow).isInfinity; + } +} + +unittest +{ + Summator!double summator = 0; + + enum double M = (cast(double)2) ^^ (double.max_exp - 1); + Tuple!(double[], double)[] tests = [ + tuple(new double[0], 0.0), + tuple([0.0], 0.0), + tuple([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50], 1e-100), + tuple([1e308, 1e308, -1e308], 1e308), + tuple([-1e308, 1e308, 1e308], 1e308), + tuple([1e308, -1e308, 1e308], 1e308), + tuple([M, M, -2.0^^1000], 1.7976930277114552e+308), + tuple([M, M, M, M, -M, -M, -M], 8.9884656743115795e+307), + tuple([2.0^^53, -0.5, -2.0^^-54], 2.0^^53-1.0), + tuple([2.0^^53, 1.0, 2.0^^-100], 2.0^^53+2.0), + tuple([2.0^^53+10.0, 1.0, 2.0^^-100], 2.0^^53+12.0), + tuple([2.0^^53-4.0, 0.5, 2.0^^-54], 2.0^^53-3.0), + tuple([M-2.0^^970, -1.0, M], 1.7976931348623157e+308), + + tuple([double.max, double.max*2.^^-54], double.max), + tuple([double.max, double.max*2.^^-53], double.infinity), + + tuple(iota(1, 1001).map!(a => 1.0/a).array , 7.4854708605503451), + tuple(iota(1, 1001).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... + tuple(iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000))]).array , -1.0), + + tuple(iota(1, 1001).map!(a => 1.0/a).retro.array , 7.4854708605503451), + tuple(iota(1, 1001).map!(a => (-1.0)^^a/a).retro.array, -0.69264743055982025), + tuple(iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000))]).retro.array , -1.0), + + + tuple([double.infinity, -double.infinity, double.nan], double.nan), + tuple([double.nan, double.infinity, -double.infinity], double.nan), + tuple([double.infinity, double.nan, double.infinity], double.nan), + tuple([double.infinity, double.infinity], double.infinity), + tuple([double.infinity, -double.infinity], double.nan), + tuple([-double.infinity, 1e308, 1e308, -double.infinity], -double.infinity), + + tuple([M-2.0^^970, 0.0, M], double.infinity), + tuple([M-2.0^^970, 1.0, M], double.infinity), + tuple([M, M], double.infinity), + tuple([M, M, -1.0], double.infinity), + tuple([M, M, M, M, -M, -M], double.infinity), + tuple([M, M, M, M, -M, M], double.infinity), + tuple([-M, -M, -M, -M], -double.infinity), + tuple([M, M, -2.^^971], double.max), + tuple([M, M, -2.^^970], double.infinity), + tuple([-2.^^970, M, M, -2.^^-1074], double.max), + tuple([M, M, -2.^^970, 2.^^-1074], double.infinity), + tuple([-M, 2.^^971, -M], -double.max), + tuple([-M, -M, 2.^^970], -double.infinity), + tuple([-M, -M, 2.^^970, 2.^^-1074], -double.max), + tuple([-2.^^-1074, -M, -M, 2.^^970], -double.infinity), + tuple([2.^^930, -2.^^980, M, M, M, -M], 1.7976931348622137e+308), + tuple([M, M, -1e307], 1.6976931348623159e+308), + tuple([1e16, 1., 1e-16], 10000000000000002.0), + ]; + foreach(test; tests) + { + foreach(t; test[0]) summator.put(t); + auto r = test[1]; + assert(summator.isNaN == r.isNaN); + assert(summator.isFinite == r.isFinite); + assert(summator.isInfinity == r.isInfinity); + auto s = summator.sum; + assert(s == r || s.isNaN && r.isNaN); + summator = 0; + } +} diff --git a/std/internal/scopebuffer.d b/std/internal/scopebuffer.d index 94ff18c6b7f..3e50e820f5b 100644 --- a/std/internal/scopebuffer.d +++ b/std/internal/scopebuffer.d @@ -199,7 +199,7 @@ struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) * A slice into the temporary buffer that is only * valid until the next put() or ScopeBuffer goes out of scope. */ - @system T[] opSlice(size_t lower, size_t upper) + @system inout(T)[] opSlice(size_t lower, size_t upper) inout in { assert(lower <= bufLen); @@ -212,7 +212,7 @@ struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) } /// ditto - @system T[] opSlice() + @system inout(T)[] opSlice() inout { assert(used <= bufLen); return buf[0 .. used]; @@ -222,7 +222,7 @@ struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) * Returns: * the element at index i. */ - ref T opIndex(size_t i) + ref inout(T) opIndex(size_t i) inout { assert(i < bufLen); return buf[i]; @@ -352,6 +352,21 @@ unittest assert(s == "hellobettyeven more"); } + +// const +unittest +{ + char[10] tmpbuf = void; + auto textbuf = ScopeBuffer!char(tmpbuf); + scope(exit) textbuf.free(); + foreach(i; 0..10) textbuf.put('w'); + const csb = textbuf; + const elem = csb[3]; + const slice0 = csb[0..5]; + const slice1 = csb[]; +} + + /********************************* * This is a slightly simpler way to create a ScopeBuffer instance * that uses type deduction. @@ -391,4 +406,3 @@ unittest c.put(s1); c.put(s2); } - diff --git a/std/numeric.d b/std/numeric.d index 30e3d602d18..1d07fb572d3 100644 --- a/std/numeric.d +++ b/std/numeric.d @@ -12,7 +12,7 @@ WIKI = Phobos/StdNumeric Copyright: Copyright Andrei Alexandrescu 2008 - 2009. License: Boost License 1.0. Authors: $(WEB erdani.org, Andrei Alexandrescu), - Don Clugston, Robert Jacques + Don Clugston, Robert Jacques, Ilya Yaroshenko (summation) Source: $(PHOBOSSRC std/_numeric.d) */ /* @@ -1369,6 +1369,244 @@ unittest 0.01)); } + +/** +Computes sum of range. +*/ +//TODO: CTFE for Precise. Needs CTFEScopeBuffer. +template fsum(SummationType, Summation summation = Summation.Precise) + if(isMutable!SummationType) +{ + static assert( + __traits(compiles, + { + SummationType a = 0.0, b, c; + c = a + b; + c = a - b; + static if(summation != Summation.Pairwise) + { + a += b; + a -= b; + } + }), summation.stringof~" isn't implemented for "~SummationType.stringof); + + + + SummationType fsum(Range)(Range r) + if( + isInputRange!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), SummationType) && + !isInfinite!Range && + ( + summation != Summation.Pairwise || + hasLength!Range && hasSlicing!Range + ) + ) + { + import std.internal.math.summation; + + static if(summation == Summation.Naive) + return sumNaive!(Range, SummationType)(r, SummationType(0.0)); + else static if(summation == Summation.Pairwise) + return sumPairwise!(Range, SummationType)(r); + else static if(summation == Summation.Kahan) + return sumKahan!(Range, SummationType)(r); + else static if(summation == Summation.KBN) + return sumKBN!(Range, SummationType)(r); + else static if(summation == Summation.KB2) + return sumKB2!(Range, SummationType)(r); + else static if(summation == Summation.Precise) + { + static if(isFloatingPoint!SummationType) + { + Summator!SummationType sum = 0; + foreach(e; r) + { + sum.put(e); + } + return sum.sum; + } + else + { + import std.complex : Complex; + static if(is(SummationType : Complex!F , F)) + { + static if(isForwardRange!Range) + { + auto s = r.save; + Summator!F sum = 0; + foreach(e; r) + { + sum.put(e.re); + } + F sumRe = sum.sum; + sum = 0; + foreach(e; s) + { + sum.put(e.im); + } + return SummationType(sumRe, sum.sum); + } + else + { + Summator!F sumRe = 0, sumIm = 0; + foreach(e; r) + { + sumRe.put(e.re); + sumIm.put(e.im); + } + return SummationType(sumRe.sum, sumIm.sum); + } + } + else static assert(0, "Precise summation isn't implemented for "~SummationType.stringof); + } + } + else static assert(0); + } +} + +///ditto +template fsum(Summation summation = Summation.Precise) +{ + Unqual!(ForeachType!Range) fsum(Range)(Range r) + { + return .fsum!(typeof(return), summation)(r); + } +} + +/// +unittest +{ + import std.math, std.algorithm, std.range; + auto ar = 1000 + .iota + .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) + .chain([-(1.7.pow(1000))]); + + //Summation.Precise is default + assert(ar.fsum == -1.0); + assert(ar.retro.fsum == -1.0); +} + +/// +unittest { + auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); + const r = 20000; + assert(r != ar.fsum!(Summation.Naive)); + assert(r != ar.fsum!(Summation.Pairwise)); + assert(r != ar.fsum!(Summation.Kahan)); + assert(r == ar.fsum!(Summation.KBN)); + assert(r == ar.fsum!(Summation.KB2)); + assert(r == ar.fsum); //Summation.Precise +} + +/** +$(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for summation user defined types. +*/ +unittest +{ + static struct Quaternion(F) + if(isFloatingPoint!F) + { + F[3] array; + + /// + and - operator overloading + Quaternion opBinary(string op)(auto ref Quaternion rhs) const + if(op == "+" || op == "-") + { + Quaternion ret = void; + foreach(i, ref e; ret.array) + mixin("e = array[i] "~op~" rhs.array[i];"); + return ret; + } + + /// += and -= operator overloading + Quaternion opOpAssign(string op)(auto ref Quaternion rhs) + if(op == "+" || op == "-") + { + Quaternion ret = void; + foreach(i, ref e; array) + mixin("e "~op~"= rhs.array[i];"); + return this; + } + + ///constructor with single FP argument + this(F f) + { + array[] = f; + } + } + + Quaternion!double q, p, r; + q.array = [0, 1, 2]; + p.array = [3, 4, 5]; + r.array = [3, 5, 7]; + + assert(r == [p, q].fsum!(Summation.Naive)); + assert(r == [p, q].fsum!(Summation.Pairwise)); + assert(r == [p, q].fsum!(Summation.Kahan)); +} + +/** +Data type for summation can be specified. +*/ +unittest { + static assert(is(typeof([1, 3, 2].fsum!double) == double)); + static assert(is(typeof([1.0,2.0].fsum!(float, Summation.KBN)) == float)); +} + +/** +All summation algorithms available for complex numbers. +*/ +unittest +{ + import std.complex; + Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; + Complex!double r = complex(10, 14); + assert(r == ar.fsum); +} + + +/** +Summation algorithms for ranges of floating point numbers or $(D Complex). +*/ +enum Summation +{ + /** + Naive summation algorithm. + */ + Naive, + + /** + $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. + */ + Pairwise, + + /** + $(LUCKY Kahan summation) algorithm. + */ + Kahan, + + /** + $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). + */ + KBN, + + /** + $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). + */ + KB2, + + /** + Full precision summation algorithm. + Returns the value of the sum, rounded to the nearest representable + floating-point number using the $(LUCKY round-half-to-even rule). + */ + Precise, + + +} + /** Normalizes values in $(D range) by multiplying each element with a number chosen such that values sum up to $(D sum). If elements in $(D diff --git a/win32.mak b/win32.mak index d1434df1b48..185a98f9d66 100644 --- a/win32.mak +++ b/win32.mak @@ -204,7 +204,8 @@ SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d SRC_STD_INTERNAL_MATH= std\internal\math\biguintcore.d \ std\internal\math\biguintnoasm.d std\internal\math\biguintx86.d \ - std\internal\math\gammafunction.d std\internal\math\errorfunction.d + std\internal\math\gammafunction.d std\internal\math\errorfunction.d \ + std\internal\math\summation.d SRC_STD_INTERNAL_WINDOWS= std\internal\windows\advapi32.d diff --git a/win64.mak b/win64.mak index 315d774877d..c4d5f8ef2a1 100644 --- a/win64.mak +++ b/win64.mak @@ -223,7 +223,8 @@ SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d SRC_STD_INTERNAL_MATH= std\internal\math\biguintcore.d \ std\internal\math\biguintnoasm.d std\internal\math\biguintx86.d \ - std\internal\math\gammafunction.d std\internal\math\errorfunction.d + std\internal\math\gammafunction.d std\internal\math\errorfunction.d \ + std\internal\math\summation.d SRC_STD_INTERNAL_WINDOWS= std\internal\windows\advapi32.d From d8fcdb4b04e6e91294232e8c77d93acb24d69544 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 8 Feb 2015 16:51:10 +0300 Subject: [PATCH 02/46] merge master --- CONTRIBUTING.md | 1 + etc/c/curl.d | 19 +- etc/c/sqlite3.d | 1367 ++- etc/c/zlib.d | 5 +- etc/c/zlib/ChangeLog | 266 +- etc/c/zlib/README | 24 +- etc/c/zlib/adler32.c | 68 +- etc/c/zlib/algorithm.txt | 2 +- etc/c/zlib/compress.c | 2 +- etc/c/zlib/crc32.c | 83 +- etc/c/zlib/crc32.h | 2 +- etc/c/zlib/deflate.c | 263 +- etc/c/zlib/deflate.h | 12 +- etc/c/zlib/example.c | 90 +- etc/c/zlib/gzguts.h | 103 +- etc/c/zlib/gzlib.c | 197 +- etc/c/zlib/gzread.c | 431 +- etc/c/zlib/gzwrite.c | 196 +- etc/c/zlib/infback.c | 16 +- etc/c/zlib/inffast.c | 6 +- etc/c/zlib/inffixed.h | 6 +- etc/c/zlib/inflate.c | 136 +- etc/c/zlib/inftrees.c | 54 +- etc/c/zlib/linux.mak | 190 +- etc/c/zlib/minigzip.c | 213 +- etc/c/zlib/osx.mak | 190 +- etc/c/zlib/trees.c | 54 +- etc/c/zlib/uncompr.c | 2 +- etc/c/zlib/win32.mak | 192 +- etc/c/zlib/win64.mak | 202 +- etc/c/zlib/zconf.h | 201 +- etc/c/zlib/zlib.3 | 18 +- etc/c/zlib/zlib.h | 345 +- etc/c/zlib/zutil.c | 26 +- etc/c/zlib/zutil.h | 103 +- index.d | 612 +- posix.mak | 81 +- std/algorithm.d | 13603 ------------------------ std/algorithm/comparison.d | 1534 +++ std/algorithm/iteration.d | 4138 +++++++ std/algorithm/mutation.d | 2203 ++++ std/algorithm/package.d | 375 + std/algorithm/searching.d | 3437 ++++++ std/algorithm/setops.d | 1323 +++ std/algorithm/sorting.d | 2731 +++++ std/array.d | 1393 +-- std/base64.d | 17 +- std/bigint.d | 213 +- std/bitmanip.d | 732 +- std/c/fenv.d | 3 + std/c/freebsd/socket.d | 41 +- std/c/linux/linux.d | 5 +- std/c/linux/linuxextern.d | 1 + std/c/linux/pthread.d | 4 +- std/c/linux/socket.d | 39 +- std/c/linux/termios.d | 4 +- std/c/linux/tipc.d | 205 +- std/c/locale.d | 3 + std/c/math.d | 5 +- std/c/osx/socket.d | 39 +- std/c/process.d | 33 +- std/c/stdarg.d | 3 + std/c/stddef.d | 3 + std/c/stdio.d | 10 +- std/c/stdlib.d | 9 +- std/c/string.d | 4 +- std/c/time.d | 3 + std/c/wcharh.d | 3 + std/c/windows/com.d | 261 +- std/c/windows/stat.d | 47 +- std/c/windows/windows.d | 3 +- std/c/windows/winsock.d | 730 +- std/compiler.d | 2 +- std/complex.d | 131 +- std/concurrency.d | 1202 ++- std/container/array.d | 484 +- std/container/binaryheap.d | 38 +- std/container/dlist.d | 326 +- std/container/package.d | 340 +- std/container/rbtree.d | 143 +- std/container/slist.d | 31 +- std/container/util.d | 69 +- std/conv.d | 396 +- std/cstream.d | 22 +- std/csv.d | 300 +- std/datetime.d | 517 +- std/demangle.d | 13 +- std/digest/crc.d | 12 +- std/digest/digest.d | 22 +- std/digest/md.d | 20 +- std/digest/ripemd.d | 20 +- std/digest/sha.d | 23 +- std/encoding.d | 199 +- std/exception.d | 453 +- std/experimental/logger/core.d | 3021 ++++++ std/experimental/logger/filelogger.d | 202 + std/experimental/logger/multilogger.d | 195 + std/experimental/logger/nulllogger.d | 36 + std/experimental/logger/package.d | 182 + std/file.d | 490 +- std/format.d | 1159 +- std/functional.d | 557 +- std/getopt.d | 145 +- std/internal/cstring.d | 24 +- std/internal/digest/sha_SSSE3.d | 4 +- std/internal/math/biguintcore.d | 178 +- std/internal/math/biguintnoasm.d | 28 +- std/internal/math/biguintx86.d | 144 +- std/internal/math/gammafunction.d | 68 +- std/internal/scopebuffer.d | 7 +- std/internal/test/dummyrange.d | 241 + std/internal/unicode_comp.d | 2 + std/internal/unicode_decomp.d | 2 + std/internal/unicode_tables.d | 11 +- std/internal/windows/advapi32.d | 2 +- std/json.d | 257 +- std/math.d | 819 +- std/mathspecial.d | 11 +- std/metastrings.d | 3 + std/mmfile.d | 77 +- std/net/curl.d | 118 +- std/net/isemail.d | 126 +- std/numeric.d | 1796 ++-- std/outbuffer.d | 76 +- std/parallelism.d | 20 +- std/path.d | 101 +- std/process.d | 165 +- std/random.d | 261 +- std/range/interfaces.d | 508 + std/{range.d => range/package.d} | 4128 ++----- std/range/primitives.d | 2232 ++++ std/regex.d | 7570 ------------- std/regex/internal/backtracking.d | 1406 +++ std/regex/internal/generator.d | 185 + std/regex/internal/ir.d | 749 ++ std/regex/internal/kickstart.d | 546 + std/regex/internal/parser.d | 1529 +++ std/regex/internal/tests.d | 939 ++ std/regex/internal/thompson.d | 947 ++ std/regex/package.d | 1428 +++ std/signals.d | 38 +- std/socket.d | 93 +- std/stdint.d | 2 +- std/stdio.d | 411 +- std/stdiobase.d | 2 +- std/stream.d | 18 +- std/string.d | 766 +- std/syserror.d | 2 +- std/traits.d | 1174 +- std/typecons.d | 1156 +- std/typelist.d | 3 +- std/typetuple.d | 4 +- std/uni.d | 484 +- std/uri.d | 29 +- std/utf.d | 289 +- std/uuid.d | 317 +- std/variant.d | 332 +- std/windows/charset.d | 4 +- std/windows/iunknown.d | 4 +- std/windows/registry.d | 18 +- std/windows/syserror.d | 157 +- std/xml.d | 48 +- std/zip.d | 180 +- std/zlib.d | 13 +- unittest.d | 9 +- win32.mak | 189 +- win64.mak | 168 +- 167 files changed, 47924 insertions(+), 34379 deletions(-) delete mode 100644 std/algorithm.d create mode 100644 std/algorithm/comparison.d create mode 100644 std/algorithm/iteration.d create mode 100644 std/algorithm/mutation.d create mode 100644 std/algorithm/package.d create mode 100644 std/algorithm/searching.d create mode 100644 std/algorithm/setops.d create mode 100644 std/algorithm/sorting.d create mode 100644 std/experimental/logger/core.d create mode 100644 std/experimental/logger/filelogger.d create mode 100644 std/experimental/logger/multilogger.d create mode 100644 std/experimental/logger/nulllogger.d create mode 100644 std/experimental/logger/package.d create mode 100644 std/internal/test/dummyrange.d create mode 100644 std/metastrings.d create mode 100644 std/range/interfaces.d rename std/{range.d => range/package.d} (72%) create mode 100644 std/range/primitives.d delete mode 100644 std/regex.d create mode 100644 std/regex/internal/backtracking.d create mode 100644 std/regex/internal/generator.d create mode 100644 std/regex/internal/ir.d create mode 100644 std/regex/internal/kickstart.d create mode 100644 std/regex/internal/parser.d create mode 100644 std/regex/internal/tests.d create mode 100644 std/regex/internal/thompson.d create mode 100644 std/regex/package.d diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2be2246cd28..2f5a1decb05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,7 @@ Guidelines for Contributing Welcome to the D community and thanks for your interest in contributing! ## [How to contribute pull requests?](http://wiki.dlang.org/Pull_Requests) +## [How to build dmd/druntime/phobos/docs?](http://wiki.dlang.org/Building_DMD) More Links ---------- diff --git a/etc/c/curl.d b/etc/c/curl.d index f4a6bb0e810..71afc34263a 100644 --- a/etc/c/curl.d +++ b/etc/c/curl.d @@ -116,7 +116,7 @@ alias socket_t curl_socket_t; /// jdrewsen - Would like to get socket error constant from std.socket by it is private atm. version(Windows) { - private import std.c.windows.windows, std.c.windows.winsock; + private import core.sys.windows.windows, core.sys.windows.winsock2; enum CURL_SOCKET_BAD = SOCKET_ERROR; } version(Posix) enum CURL_SOCKET_BAD = -1; @@ -1250,7 +1250,12 @@ enum CurlOption { /** Set authentication type for authenticated TLS */ tlsauth_type, /** the last unused */ - lastentry + lastentry, + + writedata = file, /// convenient alias + readdata = infile, /// ditto + headerdata = writeheader, /// ditto + rtspheader = httpheader, /// ditto } /// alias int CURLoption; @@ -1473,7 +1478,7 @@ void curl_formfree(curl_httppost *form); * Returns a malloc()'ed string that MUST be curl_free()ed after usage is * complete. DEPRECATED - see lib/README.curlx */ -char * curl_getenv(char *variable); +char * curl_getenv(in char *variable); /** * Name: curl_version() @@ -1580,7 +1585,7 @@ struct curl_slist * Appends a string to a linked list. If no list exists, it will be created * first. Returns the new list, after appending. */ -curl_slist * curl_slist_append(curl_slist *, char *); +curl_slist * curl_slist_append(curl_slist *, in char *); /** * Name: curl_slist_free_all() @@ -1869,7 +1874,7 @@ curl_version_info_data * curl_version_info(CURLversion ); * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ -char * curl_easy_strerror(CURLcode ); +const(char)* curl_easy_strerror(CURLcode ); /** * Name: curl_share_strerror() @@ -1880,7 +1885,7 @@ char * curl_easy_strerror(CURLcode ); * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ -char * curl_share_strerror(CURLSHcode ); +const(char)* curl_share_strerror(CURLSHcode ); /** * Name: curl_easy_pause() @@ -2197,7 +2202,7 @@ extern (C) CURLMsg * curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queu * * Returns: A pointer to a zero-terminated error message. */ -extern (C) char * curl_multi_strerror(CURLMcode ); +extern (C) const(char)* curl_multi_strerror(CURLMcode ); /** * Name: curl_multi_socket() and diff --git a/etc/c/sqlite3.d b/etc/c/sqlite3.d index b4d9f78cfb7..a26ecde2367 100644 --- a/etc/c/sqlite3.d +++ b/etc/c/sqlite3.d @@ -38,11 +38,11 @@ __gshared: /** ** CAPI3REF: Compile-Time Library Version Numbers */ -enum SQLITE_VERSION = "3.7.6.2"; +enum SQLITE_VERSION = "3.8.6"; /// Ditto -enum SQLITE_VERSION_NUMBER = 3007006; +enum SQLITE_VERSION_NUMBER = 3008006; /// Ditto -enum SQLITE_SOURCE_ID = "2011-04-17 17:25:17 154ddbc17120be2915eb03edc52af1225eb7cb5e"; +enum SQLITE_SOURCE_ID = "2014-08-15 11:46:33 9491ba7d738528f168657adb43a198238abde19e"; /** ** CAPI3REF: Run-Time Library Version Numbers @@ -82,6 +82,7 @@ alias ulong sqlite3_uint64; ** */ int sqlite3_close(sqlite3 *); +int sqlite3_close_v2(sqlite3*); /** ** The type for a callback function. @@ -94,183 +95,241 @@ alias int function (void*,int,char**, char**) sqlite3_callback; ** CAPI3REF: One-Step Query Execution Interface */ int sqlite3_exec( - sqlite3*, /** An open database */ - const(char)*sql, /** SQL to be evaluated */ - int function (void*,int,char**,char**) callback, /** Callback function */ - void *, /** 1st argument to callback */ - char **errmsg /** Error msg written here */ + sqlite3*, /** An open database */ + const(char)*sql, /** SQL to be evaluated */ + int function (void*,int,char**,char**) callback, /** Callback function */ + void *, /** 1st argument to callback */ + char **errmsg /** Error msg written here */ ); /** ** CAPI3REF: Result Codes */ enum - SQLITE_OK = 0; /** Successful result */ +{ + SQLITE_OK = 0, /** Successful result */ /* beginning-of-error-codes */ /// Ditto -enum - SQLITE_ERROR = 1, /** SQL error or missing database */ - SQLITE_INTERNAL = 2, /** Internal logic error in SQLite */ - SQLITE_PERM = 3, /** Access permission denied */ - SQLITE_ABORT = 4, /** Callback routine requested an abort */ - SQLITE_BUSY = 5, /** The database file is locked */ - SQLITE_LOCKED = 6, /** A table in the database is locked */ - SQLITE_NOMEM = 7, /** A malloc() failed */ - SQLITE_READONLY = 8, /** Attempt to write a readonly database */ - SQLITE_INTERRUPT = 9, /** Operation terminated by sqlite3_interrupt()*/ - SQLITE_IOERR = 10, /** Some kind of disk I/O error occurred */ - SQLITE_CORRUPT = 11, /** The database disk image is malformed */ - SQLITE_NOTFOUND = 12, /** Unknown opcode in sqlite3_file_control() */ - SQLITE_FULL = 13, /** Insertion failed because database is full */ - SQLITE_CANTOPEN = 14, /** Unable to open the database file */ - SQLITE_PROTOCOL = 15, /** Database lock protocol error */ - SQLITE_EMPTY = 16, /** Database is empty */ - SQLITE_SCHEMA = 17, /** The database schema changed */ - SQLITE_TOOBIG = 18, /** String or BLOB exceeds size limit */ - SQLITE_CONSTRAINT = 19, /** Abort due to constraint violation */ - SQLITE_MISMATCH = 20, /** Data type mismatch */ - SQLITE_MISUSE = 21, /** Library used incorrectly */ - SQLITE_NOLFS = 22, /** Uses OS features not supported on host */ - SQLITE_AUTH = 23, /** Authorization denied */ - SQLITE_FORMAT = 24, /** Auxiliary database format error */ - SQLITE_RANGE = 25, /** 2nd parameter to sqlite3_bind out of range */ - SQLITE_NOTADB = 26, /** File opened that is not a database file */ - SQLITE_ROW = 100, /** sqlite3_step() has another row ready */ - SQLITE_DONE = 101; /** sqlite3_step() has finished executing */ + SQLITE_ERROR = 1, /** SQL error or missing database */ + SQLITE_INTERNAL = 2, /** Internal logic error in SQLite */ + SQLITE_PERM = 3, /** Access permission denied */ + SQLITE_ABORT = 4, /** Callback routine requested an abort */ + SQLITE_BUSY = 5, /** The database file is locked */ + SQLITE_LOCKED = 6, /** A table in the database is locked */ + SQLITE_NOMEM = 7, /** A malloc() failed */ + SQLITE_READONLY = 8, /** Attempt to write a readonly database */ + SQLITE_INTERRUPT = 9, /** Operation terminated by sqlite3_interrupt()*/ + SQLITE_IOERR = 10, /** Some kind of disk I/O error occurred */ + SQLITE_CORRUPT = 11, /** The database disk image is malformed */ + SQLITE_NOTFOUND = 12, /** Unknown opcode in sqlite3_file_control() */ + SQLITE_FULL = 13, /** Insertion failed because database is full */ + SQLITE_CANTOPEN = 14, /** Unable to open the database file */ + SQLITE_PROTOCOL = 15, /** Database lock protocol error */ + SQLITE_EMPTY = 16, /** Database is empty */ + SQLITE_SCHEMA = 17, /** The database schema changed */ + SQLITE_TOOBIG = 18, /** String or BLOB exceeds size limit */ + SQLITE_CONSTRAINT = 19, /** Abort due to constraint violation */ + SQLITE_MISMATCH = 20, /** Data type mismatch */ + SQLITE_MISUSE = 21, /** Library used incorrectly */ + SQLITE_NOLFS = 22, /** Uses OS features not supported on host */ + SQLITE_AUTH = 23, /** Authorization denied */ + SQLITE_FORMAT = 24, /** Auxiliary database format error */ + SQLITE_RANGE = 25, /** 2nd parameter to sqlite3_bind out of range */ + SQLITE_NOTADB = 26, /** File opened that is not a database file */ + SQLITE_NOTICE = 27, + SQLITE_WARNING = 28, + SQLITE_ROW = 100, /** sqlite3_step() has another row ready */ + SQLITE_DONE = 101 /** sqlite3_step() has finished executing */ +} /* end-of-error-codes */ /** ** CAPI3REF: Extended Result Codes */ enum - SQLITE_IOERR_READ = (SQLITE_IOERR | (1<<8)), - SQLITE_IOERR_SHORT_READ = (SQLITE_IOERR | (2<<8)), - SQLITE_IOERR_WRITE = (SQLITE_IOERR | (3<<8)), - SQLITE_IOERR_FSYNC = (SQLITE_IOERR | (4<<8)), - SQLITE_IOERR_DIR_FSYNC = (SQLITE_IOERR | (5<<8)), - SQLITE_IOERR_TRUNCATE = (SQLITE_IOERR | (6<<8)), - SQLITE_IOERR_FSTAT = (SQLITE_IOERR | (7<<8)), - SQLITE_IOERR_UNLOCK = (SQLITE_IOERR | (8<<8)), - SQLITE_IOERR_RDLOCK = (SQLITE_IOERR | (9<<8)), - SQLITE_IOERR_DELETE = (SQLITE_IOERR | (10<<8)), - SQLITE_IOERR_BLOCKED = (SQLITE_IOERR | (11<<8)), - SQLITE_IOERR_NOMEM = (SQLITE_IOERR | (12<<8)), - SQLITE_IOERR_ACCESS = (SQLITE_IOERR | (13<<8)), - SQLITE_IOERR_CHECKRESERVEDLOCK = (SQLITE_IOERR | (14<<8)), - SQLITE_IOERR_LOCK = (SQLITE_IOERR | (15<<8)), - SQLITE_IOERR_CLOSE = (SQLITE_IOERR | (16<<8)), - SQLITE_IOERR_DIR_CLOSE = (SQLITE_IOERR | (17<<8)), - SQLITE_IOERR_SHMOPEN = (SQLITE_IOERR | (18<<8)), - SQLITE_IOERR_SHMSIZE = (SQLITE_IOERR | (19<<8)), - SQLITE_IOERR_SHMLOCK = (SQLITE_IOERR | (20<<8)), - SQLITE_LOCKED_SHAREDCACHE = (SQLITE_LOCKED | (1<<8)), - SQLITE_BUSY_RECOVERY = (SQLITE_BUSY | (1<<8)), - SQLITE_CANTOPEN_NOTEMPDIR = (SQLITE_CANTOPEN | (1<<8)); +{ + SQLITE_IOERR_READ = (SQLITE_IOERR | (1<<8)), + SQLITE_IOERR_SHORT_READ = (SQLITE_IOERR | (2<<8)), + SQLITE_IOERR_WRITE = (SQLITE_IOERR | (3<<8)), + SQLITE_IOERR_FSYNC = (SQLITE_IOERR | (4<<8)), + SQLITE_IOERR_DIR_FSYNC = (SQLITE_IOERR | (5<<8)), + SQLITE_IOERR_TRUNCATE = (SQLITE_IOERR | (6<<8)), + SQLITE_IOERR_FSTAT = (SQLITE_IOERR | (7<<8)), + SQLITE_IOERR_UNLOCK = (SQLITE_IOERR | (8<<8)), + SQLITE_IOERR_RDLOCK = (SQLITE_IOERR | (9<<8)), + SQLITE_IOERR_DELETE = (SQLITE_IOERR | (10<<8)), + SQLITE_IOERR_BLOCKED = (SQLITE_IOERR | (11<<8)), + SQLITE_IOERR_NOMEM = (SQLITE_IOERR | (12<<8)), + SQLITE_IOERR_ACCESS = (SQLITE_IOERR | (13<<8)), + SQLITE_IOERR_CHECKRESERVEDLOCK = (SQLITE_IOERR | (14<<8)), + SQLITE_IOERR_LOCK = (SQLITE_IOERR | (15<<8)), + SQLITE_IOERR_CLOSE = (SQLITE_IOERR | (16<<8)), + SQLITE_IOERR_DIR_CLOSE = (SQLITE_IOERR | (17<<8)), + SQLITE_IOERR_SHMOPEN = (SQLITE_IOERR | (18<<8)), + SQLITE_IOERR_SHMSIZE = (SQLITE_IOERR | (19<<8)), + SQLITE_IOERR_SHMLOCK = (SQLITE_IOERR | (20<<8)), + SQLITE_LOCKED_SHAREDCACHE = (SQLITE_LOCKED | (1<<8)), + SQLITE_BUSY_RECOVERY = (SQLITE_BUSY | (1<<8)), + SQLITE_CANTOPEN_NOTEMPDIR = (SQLITE_CANTOPEN | (1<<8)), + SQLITE_IOERR_GETTEMPPATH = (SQLITE_IOERR | (25<<8)), + SQLITE_IOERR_CONVPATH = (SQLITE_IOERR | (26<<8)), + SQLITE_BUSY_SNAPSHOT = (SQLITE_BUSY | (2<<8)), + SQLITE_CANTOPEN_ISDIR = (SQLITE_CANTOPEN | (2<<8)), + SQLITE_CANTOPEN_FULLPATH = (SQLITE_CANTOPEN | (3<<8)), + SQLITE_CANTOPEN_CONVPATH = (SQLITE_CANTOPEN | (4<<8)), + SQLITE_CORRUPT_VTAB = (SQLITE_CORRUPT | (1<<8)), + SQLITE_READONLY_RECOVERY = (SQLITE_READONLY | (1<<8)), + SQLITE_READONLY_CANTLOCK = (SQLITE_READONLY | (2<<8)), + SQLITE_READONLY_ROLLBACK = (SQLITE_READONLY | (3<<8)), + SQLITE_READONLY_DBMOVED = (SQLITE_READONLY | (4<<8)), + SQLITE_ABORT_ROLLBACK = (SQLITE_ABORT | (2<<8)), + SQLITE_CONSTRAINT_CHECK = (SQLITE_CONSTRAINT | (1<<8)), + SQLITE_CONSTRAINT_COMMITHOOK = (SQLITE_CONSTRAINT | (2<<8)), + SQLITE_CONSTRAINT_FOREIGNKEY = (SQLITE_CONSTRAINT | (3<<8)), + SQLITE_CONSTRAINT_FUNCTION = (SQLITE_CONSTRAINT | (4<<8)), + SQLITE_CONSTRAINT_NOTNULL = (SQLITE_CONSTRAINT | (5<<8)), + SQLITE_CONSTRAINT_PRIMARYKEY = (SQLITE_CONSTRAINT | (6<<8)), + SQLITE_CONSTRAINT_TRIGGER = (SQLITE_CONSTRAINT | (7<<8)), + SQLITE_CONSTRAINT_UNIQUE = (SQLITE_CONSTRAINT | (8<<8)), + SQLITE_CONSTRAINT_VTAB = (SQLITE_CONSTRAINT | (9<<8)), + SQLITE_CONSTRAINT_ROWID = (SQLITE_CONSTRAINT |(10<<8)), + SQLITE_NOTICE_RECOVER_WAL = (SQLITE_NOTICE | (1<<8)), + SQLITE_NOTICE_RECOVER_ROLLBACK = (SQLITE_NOTICE | (2<<8)), + SQLITE_WARNING_AUTOINDEX = (SQLITE_WARNING | (1<<8)) +} /** ** CAPI3REF: Flags For File Open Operations */ enum - SQLITE_OPEN_READONLY = 0x00000001, /** Ok for sqlite3_open_v2() */ - SQLITE_OPEN_READWRITE = 0x00000002, /** Ok for sqlite3_open_v2() */ - SQLITE_OPEN_CREATE = 0x00000004, /** Ok for sqlite3_open_v2() */ - SQLITE_OPEN_DELETEONCLOSE = 0x00000008, /** VFS only */ - SQLITE_OPEN_EXCLUSIVE = 0x00000010, /** VFS only */ - SQLITE_OPEN_AUTOPROXY = 0x00000020, /** VFS only */ - SQLITE_OPEN_MAIN_DB = 0x00000100, /** VFS only */ - SQLITE_OPEN_TEMP_DB = 0x00000200, /** VFS only */ - SQLITE_OPEN_TRANSIENT_DB = 0x00000400, /** VFS only */ - SQLITE_OPEN_MAIN_JOURNAL = 0x00000800, /** VFS only */ - SQLITE_OPEN_TEMP_JOURNAL = 0x00001000, /** VFS only */ - SQLITE_OPEN_SUBJOURNAL = 0x00002000, /** VFS only */ - SQLITE_OPEN_MASTER_JOURNAL = 0x00004000, /** VFS only */ - SQLITE_OPEN_NOMUTEX = 0x00008000, /** Ok for sqlite3_open_v2() */ - SQLITE_OPEN_FULLMUTEX = 0x00010000, /** Ok for sqlite3_open_v2() */ - SQLITE_OPEN_SHAREDCACHE = 0x00020000, /** Ok for sqlite3_open_v2() */ - SQLITE_OPEN_PRIVATECACHE = 0x00040000, /** Ok for sqlite3_open_v2() */ - SQLITE_OPEN_WAL = 0x00080000; /** VFS only */ +{ + SQLITE_OPEN_READONLY = 0x00000001, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_READWRITE = 0x00000002, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_CREATE = 0x00000004, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_DELETEONCLOSE = 0x00000008, /** VFS only */ + SQLITE_OPEN_EXCLUSIVE = 0x00000010, /** VFS only */ + SQLITE_OPEN_AUTOPROXY = 0x00000020, /** VFS only */ + SQLITE_OPEN_MAIN_DB = 0x00000100, /** VFS only */ + SQLITE_OPEN_TEMP_DB = 0x00000200, /** VFS only */ + SQLITE_OPEN_TRANSIENT_DB = 0x00000400, /** VFS only */ + SQLITE_OPEN_MAIN_JOURNAL = 0x00000800, /** VFS only */ + SQLITE_OPEN_TEMP_JOURNAL = 0x00001000, /** VFS only */ + SQLITE_OPEN_SUBJOURNAL = 0x00002000, /** VFS only */ + SQLITE_OPEN_MASTER_JOURNAL = 0x00004000, /** VFS only */ + SQLITE_OPEN_NOMUTEX = 0x00008000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_FULLMUTEX = 0x00010000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_SHAREDCACHE = 0x00020000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_PRIVATECACHE = 0x00040000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_WAL = 0x00080000 /** VFS only */ +} /** ** CAPI3REF: Device Characteristics */ enum - SQLITE_IOCAP_ATOMIC = 0x00000001, - SQLITE_IOCAP_ATOMIC512 = 0x00000002, - SQLITE_IOCAP_ATOMIC1K = 0x00000004, - SQLITE_IOCAP_ATOMIC2K = 0x00000008, - SQLITE_IOCAP_ATOMIC4K = 0x00000010, - SQLITE_IOCAP_ATOMIC8K = 0x00000020, - SQLITE_IOCAP_ATOMIC16K = 0x00000040, - SQLITE_IOCAP_ATOMIC32K = 0x00000080, - SQLITE_IOCAP_ATOMIC64K = 0x00000100, - SQLITE_IOCAP_SAFE_APPEND = 0x00000200, - SQLITE_IOCAP_SEQUENTIAL = 0x00000400, - SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 0x00000800; +{ + SQLITE_IOCAP_ATOMIC = 0x00000001, + SQLITE_IOCAP_ATOMIC512 = 0x00000002, + SQLITE_IOCAP_ATOMIC1K = 0x00000004, + SQLITE_IOCAP_ATOMIC2K = 0x00000008, + SQLITE_IOCAP_ATOMIC4K = 0x00000010, + SQLITE_IOCAP_ATOMIC8K = 0x00000020, + SQLITE_IOCAP_ATOMIC16K = 0x00000040, + SQLITE_IOCAP_ATOMIC32K = 0x00000080, + SQLITE_IOCAP_ATOMIC64K = 0x00000100, + SQLITE_IOCAP_SAFE_APPEND = 0x00000200, + SQLITE_IOCAP_SEQUENTIAL = 0x00000400, + SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 0x00000800, + SQLITE_IOCAP_POWERSAFE_OVERWRITE = 0x00001000, + SQLITE_IOCAP_IMMUTABLE = 0x00002000 +} /** ** CAPI3REF: File Locking Levels */ enum - SQLITE_LOCK_NONE = 0, - SQLITE_LOCK_SHARED = 1, - SQLITE_LOCK_RESERVED = 2, - SQLITE_LOCK_PENDING = 3, - SQLITE_LOCK_EXCLUSIVE = 4; +{ + SQLITE_LOCK_NONE = 0, + SQLITE_LOCK_SHARED = 1, + SQLITE_LOCK_RESERVED = 2, + SQLITE_LOCK_PENDING = 3, + SQLITE_LOCK_EXCLUSIVE = 4 +} /** ** CAPI3REF: Synchronization Type Flags */ enum - SQLITE_SYNC_NORMAL = 0x00002, - SQLITE_SYNC_FULL = 0x00003, - SQLITE_SYNC_DATAONLY = 0x00010; +{ + SQLITE_SYNC_NORMAL = 0x00002, + SQLITE_SYNC_FULL = 0x00003, + SQLITE_SYNC_DATAONLY = 0x00010 +} /** ** CAPI3REF: OS Interface Open File Handle */ -struct sqlite3_file { - const(sqlite3_io_methods)*pMethods; /* Methods for an open file */ -}; +struct sqlite3_file +{ + const(sqlite3_io_methods)*pMethods; /* Methods for an open file */ +} /** ** CAPI3REF: OS Interface File Virtual Methods Object */ -struct sqlite3_io_methods { - int iVersion; - int function (sqlite3_file*) xClose; - int function (sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) xRead; - int function (sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) xWrite; - int function (sqlite3_file*, sqlite3_int64 size) xTruncate; - int function (sqlite3_file*, int flags) xSync; - int function (sqlite3_file*, sqlite3_int64 *pSize) xFileSize; - int function (sqlite3_file*, int) xLock; - int function (sqlite3_file*, int) xUnlock; - int function (sqlite3_file*, int *pResOut) xCheckReservedLock; - int function (sqlite3_file*, int op, void *pArg) xFileControl; - int function (sqlite3_file*) xSectorSize; - int function (sqlite3_file*) xDeviceCharacteristics; - /* Methods above are valid for version 1 */ - int function (sqlite3_file*, int iPg, int pgsz, int, void **) xShmMap; - int function (sqlite3_file*, int offset, int n, int flags) xShmLock; - void function (sqlite3_file*) xShmBarrier; - int function (sqlite3_file*, int deleteFlag) xShmUnmap; - /* Methods above are valid for version 2 */ - /* Additional methods may be added in future releases */ -}; +struct sqlite3_io_methods +{ + int iVersion; + int function (sqlite3_file*) xClose; + int function (sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) xRead; + int function (sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) xWrite; + int function (sqlite3_file*, sqlite3_int64 size) xTruncate; + int function (sqlite3_file*, int flags) xSync; + int function (sqlite3_file*, sqlite3_int64 *pSize) xFileSize; + int function (sqlite3_file*, int) xLock; + int function (sqlite3_file*, int) xUnlock; + int function (sqlite3_file*, int *pResOut) xCheckReservedLock; + int function (sqlite3_file*, int op, void *pArg) xFileControl; + int function (sqlite3_file*) xSectorSize; + int function (sqlite3_file*) xDeviceCharacteristics; + /* Methods above are valid for version 1 */ + int function (sqlite3_file*, int iPg, int pgsz, int, void **) xShmMap; + int function (sqlite3_file*, int offset, int n, int flags) xShmLock; + void function (sqlite3_file*) xShmBarrier; + int function (sqlite3_file*, int deleteFlag) xShmUnmap; + /* Methods above are valid for version 2 */ + /* Additional methods may be added in future releases */ +} /** ** CAPI3REF: Standard File Control Opcodes */ enum - SQLITE_FCNTL_LOCKSTATE = 1, - SQLITE_GET_LOCKPROXYFILE = 2, - SQLITE_SET_LOCKPROXYFILE = 3, - SQLITE_LAST_ERRNO = 4, - SQLITE_FCNTL_SIZE_HINT = 5, - SQLITE_FCNTL_CHUNK_SIZE = 6, - SQLITE_FCNTL_FILE_POINTER = 7, - SQLITE_FCNTL_SYNC_OMITTED = 8; +{ + SQLITE_FCNTL_LOCKSTATE = 1, + SQLITE_GET_LOCKPROXYFILE = 2, + SQLITE_SET_LOCKPROXYFILE = 3, + SQLITE_LAST_ERRNO = 4, + SQLITE_FCNTL_SIZE_HINT = 5, + SQLITE_FCNTL_CHUNK_SIZE = 6, + SQLITE_FCNTL_FILE_POINTER = 7, + SQLITE_FCNTL_SYNC_OMITTED = 8, + SQLITE_FCNTL_WIN_AV_RETRY = 32, + SQLITE_FCNTL_PERSIST_WAL = 10, + SQLITE_FCNTL_OVERWRITE = 11, + SQLITE_FCNTL_VFSNAME = 12, + SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13, + SQLITE_FCNTL_PRAGMA = 14, + SQLITE_FCNTL_BUSYHANDLER = 15, + SQLITE_FCNTL_TEMPFILENAME = 16, + SQLITE_FCNTL_MMAP_SIZE = 18, + SQLITE_FCNTL_TRACE = 19, + SQLITE_FCNTL_HAS_MOVED = 20, + SQLITE_FCNTL_SYNC = 21, + SQLITE_FCNTL_COMMIT_PHASETWO = 22, + SQLITE_FCNTL_WIN_SET_HANDLE = 32 +} /** ** CAPI3REF: Mutex Handle @@ -285,61 +344,72 @@ alias void * function() xDlSymReturn; /// Ditto alias void function() sqlite3_syscall_ptr; -struct sqlite3_vfs { - int iVersion; /** Structure version number (currently 2) */ - int szOsFile; /** Size of subclassed sqlite3_file */ - int mxPathname; /** Maximum file pathname length */ - sqlite3_vfs *pNext; /** Next registered VFS */ - const(char)*zName; /** Name of this virtual file system */ - void *pAppData; /** Pointer to application-specific data */ - int function (sqlite3_vfs*, const char *zName, sqlite3_file*, +struct sqlite3_vfs +{ + int iVersion; /** Structure version number (currently 2) */ + int szOsFile; /** Size of subclassed sqlite3_file */ + int mxPathname; /** Maximum file pathname length */ + sqlite3_vfs *pNext; /** Next registered VFS */ + const(char)*zName; /** Name of this virtual file system */ + void *pAppData; /** Pointer to application-specific data */ + int function (sqlite3_vfs*, const char *zName, sqlite3_file*, int flags, int *pOutFlags) xOpen; - int function (sqlite3_vfs*, const char *zName, int syncDir) xDelete; - int function (sqlite3_vfs*, const char *zName, int flags, int *pResOut) xAccess; - int function (sqlite3_vfs*, const char *zName, int nOut, char *zOut) xFullPathname; - void* function (sqlite3_vfs*, const char *zFilename) xDlOpen; - void function (sqlite3_vfs*, int nByte, char *zErrMsg) xDlError; - xDlSymReturn function (sqlite3_vfs*,void*, const char *zSymbol) *xDlSym; - void function (sqlite3_vfs*, void*) xDlClose; - int function (sqlite3_vfs*, int nByte, char *zOut) xRandomness; - int function (sqlite3_vfs*, int microseconds) xSleep; - int function (sqlite3_vfs*, double*) xCurrentTime; - int function (sqlite3_vfs*, int, char *) xGetLastError; - /* - ** The methods above are in version 1 of the sqlite_vfs object - ** definition. Those that follow are added in version 2 or later - */ - int function (sqlite3_vfs*, sqlite3_int64*) xCurrentTimeInt64; - /* - ** The methods above are in versions 1 and 2 of the sqlite_vfs object. - ** New fields may be appended in figure versions. The iVersion - ** value will increment whenever this happens. - */ -}; + int function (sqlite3_vfs*, const char *zName, int syncDir) xDelete; + int function (sqlite3_vfs*, const char *zName, int flags, int *pResOut) xAccess; + int function (sqlite3_vfs*, const char *zName, int nOut, char *zOut) xFullPathname; + void* function (sqlite3_vfs*, const char *zFilename) xDlOpen; + void function (sqlite3_vfs*, int nByte, char *zErrMsg) xDlError; + xDlSymReturn function (sqlite3_vfs*,void*, const char *zSymbol) *xDlSym; + void function (sqlite3_vfs*, void*) xDlClose; + int function (sqlite3_vfs*, int nByte, char *zOut) xRandomness; + int function (sqlite3_vfs*, int microseconds) xSleep; + int function (sqlite3_vfs*, double*) xCurrentTime; + int function (sqlite3_vfs*, int, char *) xGetLastError; + /* + ** The methods above are in version 1 of the sqlite_vfs object + ** definition. Those that follow are added in version 2 or later + */ + int function (sqlite3_vfs*, sqlite3_int64*) xCurrentTimeInt64; + /* + ** The methods above are in versions 1 and 2 of the sqlite_vfs object. + ** Those below are for version 3 and greater. + */ + int function(sqlite3_vfs*, const char * zName, sqlite3_syscall_ptr) xSetSystemCall; + sqlite3_syscall_ptr function(sqlite3_vfs*, const char * zName) xGetSystemCall; + const(char)* function(sqlite3_vfs*, const char * zName) xNextSystemCall; + /* + ** The methods above are in versions 1 through 3 of the sqlite_vfs object. + ** New fields may be appended in figure versions. The iVersion + ** value will increment whenever this happens. + */ +} /** ** CAPI3REF: Flags for the xAccess VFS method */ enum - SQLITE_ACCESS_EXISTS = 0, +{ + SQLITE_ACCESS_EXISTS = 0, - SQLITE_ACCESS_READWRITE = 1, /** Used by PRAGMA temp_store_directory */ - SQLITE_ACCESS_READ = 2; /** Unused */ + SQLITE_ACCESS_READWRITE = 1, /** Used by PRAGMA temp_store_directory */ + SQLITE_ACCESS_READ = 2 /** Unused */ +} /** ** CAPI3REF: Flags for the xShmLock VFS method */ enum - SQLITE_SHM_UNLOCK = 1, - SQLITE_SHM_LOCK = 2, - SQLITE_SHM_SHARED = 4, - SQLITE_SHM_EXCLUSIVE = 8; +{ + SQLITE_SHM_UNLOCK = 1, + SQLITE_SHM_LOCK = 2, + SQLITE_SHM_SHARED = 4, + SQLITE_SHM_EXCLUSIVE = 8 +} /** ** CAPI3REF: Maximum xShmLock index */ -enum - SQLITE_SHM_NLOCK = 8; +enum SQLITE_SHM_NLOCK = 8; /** @@ -366,47 +436,57 @@ int sqlite3_db_config(sqlite3*, int op, ...); /** ** CAPI3REF: Memory Allocation Routines */ -struct sqlite3_mem_methods { - void* function (int) xMalloc; /** Memory allocation function */ - void function (void*) xFree; /** Free a prior allocation */ - void* function (void*,int) xRealloc; /** Resize an allocation */ - int function (void*) xSize; /** Return the size of an allocation */ - int function (int) xRoundup; /** Round up request size to allocation size */ - int function (void*) xInit; /** Initialize the memory allocator */ - void function (void*) xShutdown; /** Deinitialize the memory allocator */ - void *pAppData; /** Argument to xInit() and xShutdown() */ -}; +struct sqlite3_mem_methods +{ + void* function (int) xMalloc; /** Memory allocation function */ + void function (void*) xFree; /** Free a prior allocation */ + void* function (void*,int) xRealloc; /** Resize an allocation */ + int function (void*) xSize; /** Return the size of an allocation */ + int function (int) xRoundup; /** Round up request size to allocation size */ + int function (void*) xInit; /** Initialize the memory allocator */ + void function (void*) xShutdown; /** Deinitialize the memory allocator */ + void *pAppData; /** Argument to xInit() and xShutdown() */ +} /** ** CAPI3REF: Configuration Options */ enum - SQLITE_CONFIG_SINGLETHREAD = 1, /** nil */ - SQLITE_CONFIG_MULTITHREAD = 2, /** nil */ - SQLITE_CONFIG_SERIALIZED = 3, /** nil */ - SQLITE_CONFIG_MALLOC = 4, /** sqlite3_mem_methods* */ - SQLITE_CONFIG_GETMALLOC = 5, /** sqlite3_mem_methods* */ - SQLITE_CONFIG_SCRATCH = 6, /** void*, int sz, int N */ - SQLITE_CONFIG_PAGECACHE = 7, /** void*, int sz, int N */ - SQLITE_CONFIG_HEAP = 8, /** void*, int nByte, int min */ - SQLITE_CONFIG_MEMSTATUS = 9, /** boolean */ - SQLITE_CONFIG_MUTEX = 10, /** sqlite3_mutex_methods* */ - SQLITE_CONFIG_GETMUTEX = 11; /** sqlite3_mutex_methods* */ +{ + SQLITE_CONFIG_SINGLETHREAD = 1, /** nil */ + SQLITE_CONFIG_MULTITHREAD = 2, /** nil */ + SQLITE_CONFIG_SERIALIZED = 3, /** nil */ + SQLITE_CONFIG_MALLOC = 4, /** sqlite3_mem_methods* */ + SQLITE_CONFIG_GETMALLOC = 5, /** sqlite3_mem_methods* */ + SQLITE_CONFIG_SCRATCH = 6, /** void*, int sz, int N */ + SQLITE_CONFIG_PAGECACHE = 7, /** void*, int sz, int N */ + SQLITE_CONFIG_HEAP = 8, /** void*, int nByte, int min */ + SQLITE_CONFIG_MEMSTATUS = 9, /** boolean */ + SQLITE_CONFIG_MUTEX = 10, /** sqlite3_mutex_methods* */ + SQLITE_CONFIG_GETMUTEX = 11, /** sqlite3_mutex_methods* */ /* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ -/// Ditto -enum - SQLITE_CONFIG_LOOKASIDE = 13, /** int int */ - SQLITE_CONFIG_PCACHE = 14, /** sqlite3_pcache_methods* */ - SQLITE_CONFIG_GETPCACHE = 15, /** sqlite3_pcache_methods* */ - SQLITE_CONFIG_LOG = 16; /** xFunc, void* */ + SQLITE_CONFIG_LOOKASIDE = 13, /** int int */ + SQLITE_CONFIG_PCACHE = 14, /** sqlite3_pcache_methods* */ + SQLITE_CONFIG_GETPCACHE = 15, /** sqlite3_pcache_methods* */ + SQLITE_CONFIG_LOG = 16, /** xFunc, void* */ + SQLITE_CONFIG_URI = 17, + SQLITE_CONFIG_PCACHE2 = 18, + SQLITE_CONFIG_GETPCACHE2 = 19, + SQLITE_CONFIG_COVERING_INDEX_SCAN2 = 20, + SQLITE_CONFIG_SQLLOG2 = 21, + SQLITE_CONFIG_MMAP_SIZE2 = 22, + SQLITE_CONFIG_WIN32_HEAPSIZE = 23 +} /** ** CAPI3REF: Database Connection Configuration Options */ enum - SQLITE_DBCONFIG_LOOKASIDE = 1001, /** void* int int */ - SQLITE_DBCONFIG_ENABLE_FKEY = 1002, /** int int* */ - SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003; /** int int* */ +{ + SQLITE_DBCONFIG_LOOKASIDE = 1001, /** void* int int */ + SQLITE_DBCONFIG_ENABLE_FKEY = 1002, /** int int* */ + SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003 /** int int* */ +} /** @@ -455,12 +535,12 @@ int sqlite3_busy_timeout(sqlite3*, int ms); ** CAPI3REF: Convenience Routines For Running Queries */ int sqlite3_get_table( - sqlite3 *db, /** An open database */ - const(char)*zSql, /** SQL to be evaluated */ - char ***pazResult, /** Results of the query */ - int *pnRow, /** Number of result rows written here */ - int *pnColumn, /** Number of result columns written here */ - char **pzErrmsg /** Error msg written here */ + sqlite3 *db, /** An open database */ + const(char)*zSql, /** SQL to be evaluated */ + char ***pazResult, /** Results of the query */ + int *pnRow, /** Number of result rows written here */ + int *pnColumn, /** Number of result columns written here */ + char **pzErrmsg /** Error msg written here */ ); /// void sqlite3_free_table(char **result); @@ -500,56 +580,61 @@ void sqlite3_randomness(int N, void *P); ** CAPI3REF: Compile-Time Authorization Callbacks */ int sqlite3_set_authorizer( - sqlite3*, - int function (void*,int,const char*,const char*,const char*,const char*) xAuth, - void *pUserData + sqlite3*, + int function (void*,int,const char*,const char*,const char*,const char*) xAuth, + void *pUserData ); /** ** CAPI3REF: Authorizer Return Codes */ enum - SQLITE_DENY = 1, /** Abort the SQL statement with an error */ - SQLITE_IGNORE = 2; /** Don't allow access, but don't generate an error */ +{ + SQLITE_DENY = 1, /** Abort the SQL statement with an error */ + SQLITE_IGNORE = 2 /** Don't allow access, but don't generate an error */ +} /** ** CAPI3REF: Authorizer Action Codes */ /******************************************* 3rd ************ 4th ***********/ enum - SQLITE_CREATE_INDEX = 1, /** Index Name Table Name */ - SQLITE_CREATE_TABLE = 2, /** Table Name NULL */ - SQLITE_CREATE_TEMP_INDEX = 3, /** Index Name Table Name */ - SQLITE_CREATE_TEMP_TABLE = 4, /** Table Name NULL */ - SQLITE_CREATE_TEMP_TRIGGER = 5, /** Trigger Name Table Name */ - SQLITE_CREATE_TEMP_VIEW = 6, /** View Name NULL */ - SQLITE_CREATE_TRIGGER = 7, /** Trigger Name Table Name */ - SQLITE_CREATE_VIEW = 8, /** View Name NULL */ - SQLITE_DELETE = 9, /** Table Name NULL */ - SQLITE_DROP_INDEX = 10, /** Index Name Table Name */ - SQLITE_DROP_TABLE = 11, /** Table Name NULL */ - SQLITE_DROP_TEMP_INDEX = 12, /** Index Name Table Name */ - SQLITE_DROP_TEMP_TABLE = 13, /** Table Name NULL */ - SQLITE_DROP_TEMP_TRIGGER = 14, /** Trigger Name Table Name */ - SQLITE_DROP_TEMP_VIEW = 15, /** View Name NULL */ - SQLITE_DROP_TRIGGER = 16, /** Trigger Name Table Name */ - SQLITE_DROP_VIEW = 17, /** View Name NULL */ - SQLITE_INSERT = 18, /** Table Name NULL */ - SQLITE_PRAGMA = 19, /** Pragma Name 1st arg or NULL */ - SQLITE_READ = 20, /** Table Name Column Name */ - SQLITE_SELECT = 21, /** NULL NULL */ - SQLITE_TRANSACTION = 22, /** Operation NULL */ - SQLITE_UPDATE = 23, /** Table Name Column Name */ - SQLITE_ATTACH = 24, /** Filename NULL */ - SQLITE_DETACH = 25, /** Database Name NULL */ - SQLITE_ALTER_TABLE = 26, /** Database Name Table Name */ - SQLITE_REINDEX = 27, /** Index Name NULL */ - SQLITE_ANALYZE = 28, /** Table Name NULL */ - SQLITE_CREATE_VTABLE = 29, /** Table Name Module Name */ - SQLITE_DROP_VTABLE = 30, /** Table Name Module Name */ - SQLITE_FUNCTION = 31, /** NULL Function Name */ - SQLITE_SAVEPOINT = 32, /** Operation Savepoint Name */ - SQLITE_COPY = 0; /** No longer used */ +{ + SQLITE_CREATE_INDEX = 1, /** Index Name Table Name */ + SQLITE_CREATE_TABLE = 2, /** Table Name NULL */ + SQLITE_CREATE_TEMP_INDEX = 3, /** Index Name Table Name */ + SQLITE_CREATE_TEMP_TABLE = 4, /** Table Name NULL */ + SQLITE_CREATE_TEMP_TRIGGER = 5, /** Trigger Name Table Name */ + SQLITE_CREATE_TEMP_VIEW = 6, /** View Name NULL */ + SQLITE_CREATE_TRIGGER = 7, /** Trigger Name Table Name */ + SQLITE_CREATE_VIEW = 8, /** View Name NULL */ + SQLITE_DELETE = 9, /** Table Name NULL */ + SQLITE_DROP_INDEX = 10, /** Index Name Table Name */ + SQLITE_DROP_TABLE = 11, /** Table Name NULL */ + SQLITE_DROP_TEMP_INDEX = 12, /** Index Name Table Name */ + SQLITE_DROP_TEMP_TABLE = 13, /** Table Name NULL */ + SQLITE_DROP_TEMP_TRIGGER = 14, /** Trigger Name Table Name */ + SQLITE_DROP_TEMP_VIEW = 15, /** View Name NULL */ + SQLITE_DROP_TRIGGER = 16, /** Trigger Name Table Name */ + SQLITE_DROP_VIEW = 17, /** View Name NULL */ + SQLITE_INSERT = 18, /** Table Name NULL */ + SQLITE_PRAGMA = 19, /** Pragma Name 1st arg or NULL */ + SQLITE_READ = 20, /** Table Name Column Name */ + SQLITE_SELECT = 21, /** NULL NULL */ + SQLITE_TRANSACTION = 22, /** Operation NULL */ + SQLITE_UPDATE = 23, /** Table Name Column Name */ + SQLITE_ATTACH = 24, /** Filename NULL */ + SQLITE_DETACH = 25, /** Database Name NULL */ + SQLITE_ALTER_TABLE = 26, /** Database Name Table Name */ + SQLITE_REINDEX = 27, /** Index Name NULL */ + SQLITE_ANALYZE = 28, /** Table Name NULL */ + SQLITE_CREATE_VTABLE = 29, /** Table Name Module Name */ + SQLITE_DROP_VTABLE = 30, /** Table Name Module Name */ + SQLITE_FUNCTION = 31, /** NULL Function Name */ + SQLITE_SAVEPOINT = 32, /** Operation Savepoint Name */ + SQLITE_COPY = 0, /** No longer used */ + SQLITE_RECURSIVE = 33 +} /** ** CAPI3REF: Tracing And Profiling Functions @@ -568,22 +653,31 @@ void sqlite3_progress_handler(sqlite3*, int, int function (void*), void*); ** CAPI3REF: Opening A New Database Connection */ int sqlite3_open( - const(char)*filename, /** Database filename (UTF-8) */ - sqlite3 **ppDb /** OUT: SQLite db handle */ + const(char)*filename, /** Database filename (UTF-8) */ + sqlite3 **ppDb /** OUT: SQLite db handle */ ); /// Ditto int sqlite3_open16( - const(void)*filename, /** Database filename (UTF-16) */ - sqlite3 **ppDb /** OUT: SQLite db handle */ + const(void)*filename, /** Database filename (UTF-16) */ + sqlite3 **ppDb /** OUT: SQLite db handle */ ); /// Ditto int sqlite3_open_v2( - const(char)*filename, /** Database filename (UTF-8) */ - sqlite3 **ppDb, /** OUT: SQLite db handle */ - int flags, /** Flags */ - const(char)*zVfs /** Name of VFS module to use */ + const(char)*filename, /** Database filename (UTF-8) */ + sqlite3 **ppDb, /** OUT: SQLite db handle */ + int flags, /** Flags */ + const(char)*zVfs /** Name of VFS module to use */ ); +/* +** CAPI3REF: Obtain Values For URI Parameters +*/ +const(char)* sqlite3_uri_parameter(const(char)* zFilename, const(char)* zParam); +/// Ditto +int sqlite3_uri_boolean(const(char)* zFile, const(char)* zParam, int bDefault); +/// Ditto +sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); + /** ** CAPI3REF: Error Codes And Messages */ @@ -609,51 +703,53 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** CAPI3REF: Run-Time Limit Categories */ enum - SQLITE_LIMIT_LENGTH = 0, - SQLITE_LIMIT_SQL_LENGTH = 1, - SQLITE_LIMIT_COLUMN = 2, - SQLITE_LIMIT_EXPR_DEPTH = 3, - SQLITE_LIMIT_COMPOUND_SELECT = 4, - SQLITE_LIMIT_VDBE_OP = 5, - SQLITE_LIMIT_FUNCTION_ARG = 6, - SQLITE_LIMIT_ATTACHED = 7, - SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8, - SQLITE_LIMIT_VARIABLE_NUMBER = 9, - SQLITE_LIMIT_TRIGGER_DEPTH = 10; +{ + SQLITE_LIMIT_LENGTH = 0, + SQLITE_LIMIT_SQL_LENGTH = 1, + SQLITE_LIMIT_COLUMN = 2, + SQLITE_LIMIT_EXPR_DEPTH = 3, + SQLITE_LIMIT_COMPOUND_SELECT = 4, + SQLITE_LIMIT_VDBE_OP = 5, + SQLITE_LIMIT_FUNCTION_ARG = 6, + SQLITE_LIMIT_ATTACHED = 7, + SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8, + SQLITE_LIMIT_VARIABLE_NUMBER = 9, + SQLITE_LIMIT_TRIGGER_DEPTH = 10 +} /** ** CAPI3REF: Compiling An SQL Statement */ int sqlite3_prepare( - sqlite3 *db, /** Database handle */ - const(char)*zSql, /** SQL statement, UTF-8 encoded */ - int nByte, /** Maximum length of zSql in bytes. */ - sqlite3_stmt **ppStmt, /** OUT: Statement handle */ - const(char*)*pzTail /** OUT: Pointer to unused portion of zSql */ + sqlite3 *db, /** Database handle */ + const(char)*zSql, /** SQL statement, UTF-8 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(char*)*pzTail /** OUT: Pointer to unused portion of zSql */ ); /// Ditto int sqlite3_prepare_v2( - sqlite3 *db, /** Database handle */ - const(char)*zSql, /** SQL statement, UTF-8 encoded */ - int nByte, /** Maximum length of zSql in bytes. */ - sqlite3_stmt **ppStmt, /** OUT: Statement handle */ - const(char*)*pzTail /** OUT: Pointer to unused portion of zSql */ + sqlite3 *db, /** Database handle */ + const(char)*zSql, /** SQL statement, UTF-8 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(char*)*pzTail /** OUT: Pointer to unused portion of zSql */ ); /// Ditto int sqlite3_prepare16( - sqlite3 *db, /** Database handle */ - const(void)*zSql, /** SQL statement, UTF-16 encoded */ - int nByte, /** Maximum length of zSql in bytes. */ - sqlite3_stmt **ppStmt, /** OUT: Statement handle */ - const(void*)*pzTail /** OUT: Pointer to unused portion of zSql */ + sqlite3 *db, /** Database handle */ + const(void)*zSql, /** SQL statement, UTF-16 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(void*)*pzTail /** OUT: Pointer to unused portion of zSql */ ); /// Ditto int sqlite3_prepare16_v2( - sqlite3 *db, /** Database handle */ - const(void)*zSql, /** SQL statement, UTF-16 encoded */ - int nByte, /** Maximum length of zSql in bytes. */ - sqlite3_stmt **ppStmt, /** OUT: Statement handle */ - const(void*)*pzTail /** OUT: Pointer to unused portion of zSql */ + sqlite3 *db, /** Database handle */ + const(void)*zSql, /** SQL statement, UTF-16 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(void*)*pzTail /** OUT: Pointer to unused portion of zSql */ ); /** @@ -765,11 +861,13 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); ** CAPI3REF: Fundamental Datatypes */ enum - SQLITE_INTEGER = 1, - SQLITE_FLOAT = 2, - SQLITE_BLOB = 4, - SQLITE_NULL = 5, - SQLITE3_TEXT = 3; +{ + SQLITE_INTEGER = 1, + SQLITE_FLOAT = 2, + SQLITE_BLOB = 4, + SQLITE_NULL = 5, + SQLITE3_TEXT = 3 +} /** ** CAPI3REF: Result Values From A Query @@ -808,37 +906,37 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** CAPI3REF: Create Or Redefine SQL Functions */ int sqlite3_create_function( - sqlite3 *db, - const(char)*zFunctionName, - int nArg, - int eTextRep, - void *pApp, - void function (sqlite3_context*,int,sqlite3_value**) xFunc, - void function (sqlite3_context*,int,sqlite3_value**) xStep, - void function (sqlite3_context*) xFinal + sqlite3 *db, + const(char)*zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void function (sqlite3_context*,int,sqlite3_value**) xFunc, + void function (sqlite3_context*,int,sqlite3_value**) xStep, + void function (sqlite3_context*) xFinal ); /// Ditto int sqlite3_create_function16( - sqlite3 *db, - const(void)*zFunctionName, - int nArg, - int eTextRep, - void *pApp, - void function (sqlite3_context*,int,sqlite3_value**) xFunc, - void function (sqlite3_context*,int,sqlite3_value**) xStep, - void function (sqlite3_context*) xFinal + sqlite3 *db, + const(void)*zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void function (sqlite3_context*,int,sqlite3_value**) xFunc, + void function (sqlite3_context*,int,sqlite3_value**) xStep, + void function (sqlite3_context*) xFinal ); /// Ditto int sqlite3_create_function_v2( - sqlite3 *db, - const(char)*zFunctionName, - int nArg, - int eTextRep, - void *pApp, - void function (sqlite3_context*,int,sqlite3_value**) xFunc, - void function (sqlite3_context*,int,sqlite3_value**) xStep, - void function (sqlite3_context*) xFinal, - void function (void*) xDestroy + sqlite3 *db, + const(char)*zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void function (sqlite3_context*,int,sqlite3_value**) xFunc, + void function (sqlite3_context*,int,sqlite3_value**) xStep, + void function (sqlite3_context*) xFinal, + void function (void*) xDestroy ); /** @@ -848,14 +946,34 @@ int sqlite3_create_function_v2( ** text encodings supported by SQLite. */ enum - SQLITE_UTF8 = 1, - SQLITE_UTF16LE = 2, - SQLITE_UTF16BE = 3; +{ + SQLITE_UTF8 = 1, + SQLITE_UTF16LE = 2, + SQLITE_UTF16BE = 3 +} /// Ditto enum - SQLITE_UTF16 = 4, /** Use native byte order */ - SQLITE_ANY = 5, /** sqlite3_create_function only */ - SQLITE_UTF16_ALIGNED = 8; /** sqlite3_create_collation only */ +{ + SQLITE_UTF16 = 4, /** Use native byte order */ + SQLITE_ANY = 5, /** sqlite3_create_function only */ + SQLITE_UTF16_ALIGNED = 8 /** sqlite3_create_collation only */ +} + +/** +** CAPI3REF: Function Flags +*/ +enum SQLITE_DETERMINISTIC = 0x800; + +/** +** CAPI3REF: Deprecated Functions +*/ +deprecated int sqlite3_aggregate_count(sqlite3_context*); +deprecated int sqlite3_expired(sqlite3_stmt*); +deprecated int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +deprecated int sqlite3_global_recover(); +deprecated void sqlite3_thread_cleanup(); +deprecated int sqlite3_memory_alarm(void function(void*,sqlite3_int64,int), + void*,sqlite3_int64); /** ** CAPI3REF: Obtaining SQL Function Parameter Values @@ -913,8 +1031,10 @@ void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void function (void*)); alias void function (void*) sqlite3_destructor_type; /// Ditto enum - SQLITE_STATIC = (cast(sqlite3_destructor_type) 0), - SQLITE_TRANSIENT = (cast (sqlite3_destructor_type) -1); +{ + SQLITE_STATIC = (cast(sqlite3_destructor_type) 0), + SQLITE_TRANSIENT = (cast (sqlite3_destructor_type) -1) +} /** ** CAPI3REF: Setting The Result Of An SQL Function @@ -955,49 +1075,55 @@ void sqlite3_result_zeroblob(sqlite3_context*, int n); ** CAPI3REF: Define New Collating Sequences */ int sqlite3_create_collation( - sqlite3*, - const(char)*zName, - int eTextRep, - void *pArg, - int function (void*,int,const void*,int,const void*) xCompare + sqlite3*, + const(char)*zName, + int eTextRep, + void *pArg, + int function (void*,int,const void*,int,const void*) xCompare ); /// Ditto int sqlite3_create_collation_v2( - sqlite3*, - const(char)*zName, - int eTextRep, - void *pArg, - int function (void*,int,const void*,int,const void*) xCompare, - void function (void*) xDestroy + sqlite3*, + const(char)*zName, + int eTextRep, + void *pArg, + int function (void*,int,const void*,int,const void*) xCompare, + void function (void*) xDestroy ); /// Ditto int sqlite3_create_collation16( - sqlite3*, - const(void)*zName, - int eTextRep, - void *pArg, - int function (void*,int,const void*,int,const void*) xCompare + sqlite3*, + const(void)*zName, + int eTextRep, + void *pArg, + int function (void*,int,const void*,int,const void*) xCompare ); /** ** CAPI3REF: Collation Needed Callbacks */ int sqlite3_collation_needed( - sqlite3*, - void*, - void function (void*,sqlite3*,int eTextRep,const char*) + sqlite3*, + void*, + void function (void*,sqlite3*,int eTextRep,const char*) ); /// Ditto int sqlite3_collation_needed16( - sqlite3*, - void*, - void function (void*,sqlite3*,int eTextRep,const void*) + sqlite3*, + void*, + void function (void*,sqlite3*,int eTextRep,const void*) ); /// int sqlite3_key( - sqlite3 *db, /** Database to be rekeyed */ - const(void)*pKey, int nKey /** The key */ + sqlite3 *db, /** Database to be rekeyed */ + const(void)*pKey, int nKey /** The key */ +); +/// Ditto +int sqlite3_key_v2( + sqlite3 *db, /* Database to be rekeyed */ + const(char)* zDbName, /* Name of the database */ + const(void)* pKey, int nKey /* The key */ ); /** @@ -1009,8 +1135,13 @@ int sqlite3_key( ** of SQLite. */ int sqlite3_rekey( - sqlite3 *db, /** Database to be rekeyed */ - const(void)*pKey, int nKey /** The new key */ + sqlite3 *db, /** Database to be rekeyed */ + const(void)*pKey, int nKey /** The new key */ +); +int sqlite3_rekey_v2( + sqlite3 *db, /* Database to be rekeyed */ + const(char)* zDbName, /* Name of the database */ + const(void)* pKey, int nKey /* The new key */ ); /** @@ -1018,7 +1149,7 @@ int sqlite3_rekey( ** activated, none of the SEE routines will work. */ void sqlite3_activate_see( - const(char)*zPassPhrase /** Activation phrase */ + const(char)*zPassPhrase /** Activation phrase */ ); /** @@ -1026,7 +1157,7 @@ void sqlite3_activate_see( ** activated, none of the CEROD routines will work. */ void sqlite3_activate_cerod( - const(char)*zPassPhrase /** Activation phrase */ + const(char)*zPassPhrase /** Activation phrase */ ); /** @@ -1050,6 +1181,16 @@ int sqlite3_get_autocommit(sqlite3*); sqlite3 *sqlite3_db_handle(sqlite3_stmt*); /** +** CAPI3REF: Return The Filename For A Database Connection +*/ +const(char)* sqlite3_db_filename(sqlite3 *db, const char* zDbName); + +/** +** CAPI3REF: Determine if a database is read-only +*/ +int sqlite3_db_readonly(sqlite3 *db, const char * zDbName); + +/* ** CAPI3REF: Find the next prepared statement */ sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); @@ -1065,9 +1206,9 @@ void *sqlite3_rollback_hook(sqlite3*, void function (void *), void*); ** CAPI3REF: Data Change Notification Callbacks */ void *sqlite3_update_hook( - sqlite3*, - void function (void *,int ,char *, char *, sqlite3_int64), - void* + sqlite3*, + void function (void *,int ,char *, char *, sqlite3_int64), + void* ); /** @@ -1081,33 +1222,43 @@ int sqlite3_enable_shared_cache(int); int sqlite3_release_memory(int); /** +** CAPI3REF: Free Memory Used By A Database Connection +*/ +int sqlite3_db_release_memory(sqlite3*); + +/* ** CAPI3REF: Impose A Limit On Heap Size */ sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +/** +** CAPI3REF: Deprecated Soft Heap Limit Interface +*/ +deprecated void sqlite3_soft_heap_limit(int N); + /** ** CAPI3REF: Extract Metadata About A Column Of A Table */ int sqlite3_table_column_metadata( - sqlite3 *db, /** Connection handle */ - const(char)*zDbName, /** Database name or NULL */ - const(char)*zTableName, /** Table name */ - const(char)*zColumnName, /** Column name */ - char **pzDataType, /** OUTPUT: Declared data type */ - char **pzCollSeq, /** OUTPUT: Collation sequence name */ - int *pNotNull, /** OUTPUT: True if NOT NULL constraint exists */ - int *pPrimaryKey, /** OUTPUT: True if column part of PK */ - int *pAutoinc /** OUTPUT: True if column is auto-increment */ + sqlite3 *db, /** Connection handle */ + const(char)*zDbName, /** Database name or NULL */ + const(char)*zTableName, /** Table name */ + const(char)*zColumnName, /** Column name */ + char **pzDataType, /** OUTPUT: Declared data type */ + char **pzCollSeq, /** OUTPUT: Collation sequence name */ + int *pNotNull, /** OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /** OUTPUT: True if column part of PK */ + int *pAutoinc /** OUTPUT: True if column is auto-increment */ ); /** ** CAPI3REF: Load An Extension */ int sqlite3_load_extension( - sqlite3 *db, /** Load the extension into this database connection */ - const(char)*zFile, /** Name of the shared library containing extension */ - const(char)*zProc, /** Entry point. Derived from zFile if 0 */ - char **pzErrMsg /** Put error message here if not 0 */ + sqlite3 *db, /** Load the extension into this database connection */ + const(char)*zFile, /** Name of the shared library containing extension */ + const(char)*zProc, /** Entry point. Derived from zFile if 0 */ + char **pzErrMsg /** Put error message here if not 0 */ ); /** @@ -1120,6 +1271,11 @@ int sqlite3_enable_load_extension(sqlite3 *db, int onoff); */ int sqlite3_auto_extension(void function () xEntryPoint); +/** +** CAPI3REF: Cancel Automatic Extension Loading +*/ +int sqlite3_cancel_auto_extension(void function() xEntryPoint); + /** ** CAPI3REF: Reset Automatic Extension Loading */ @@ -1141,114 +1297,123 @@ void sqlite3_reset_auto_extension(); alias void function (sqlite3_context*,int,sqlite3_value**) mapFunction; /// Ditto -struct sqlite3_module { - int iVersion; - int function (sqlite3*, void *pAux, +struct sqlite3_module +{ + int iVersion; + int function (sqlite3*, void *pAux, int argc, const char **argv, sqlite3_vtab **ppVTab, char**) xCreate; - int function (sqlite3*, void *pAux, + int function (sqlite3*, void *pAux, int argc, const char **argv, sqlite3_vtab **ppVTab, char**) xConnect; - int function (sqlite3_vtab *pVTab, sqlite3_index_info*) xBestIndex; - int function (sqlite3_vtab *pVTab) xDisconnect; - int function (sqlite3_vtab *pVTab) xDestroy; - int function (sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) xOpen; - int function (sqlite3_vtab_cursor*) xClose; - int function (sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int function (sqlite3_vtab *pVTab, sqlite3_index_info*) xBestIndex; + int function (sqlite3_vtab *pVTab) xDisconnect; + int function (sqlite3_vtab *pVTab) xDestroy; + int function (sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) xOpen; + int function (sqlite3_vtab_cursor*) xClose; + int function (sqlite3_vtab_cursor*, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) xFilter; - int function (sqlite3_vtab_cursor*) xNext; - int function (sqlite3_vtab_cursor*) xEof; - int function (sqlite3_vtab_cursor*, sqlite3_context*, int) xColumn; - int function (sqlite3_vtab_cursor*, sqlite3_int64 *pRowid) xRowid; - int function (sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *) xUpdate; - int function (sqlite3_vtab *pVTab) xBegin; - int function (sqlite3_vtab *pVTab) xSync; - int function (sqlite3_vtab *pVTab) xCommit; - int function (sqlite3_vtab *pVTab) xRollback; - int function (sqlite3_vtab *pVtab, int nArg, const char *zName, + int function (sqlite3_vtab_cursor*) xNext; + int function (sqlite3_vtab_cursor*) xEof; + int function (sqlite3_vtab_cursor*, sqlite3_context*, int) xColumn; + int function (sqlite3_vtab_cursor*, sqlite3_int64 *pRowid) xRowid; + int function (sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *) xUpdate; + int function (sqlite3_vtab *pVTab) xBegin; + int function (sqlite3_vtab *pVTab) xSync; + int function (sqlite3_vtab *pVTab) xCommit; + int function (sqlite3_vtab *pVTab) xRollback; + int function (sqlite3_vtab *pVtab, int nArg, const char *zName, mapFunction*, void **ppArg) xFindFunction; - int function (sqlite3_vtab *pVtab, const char *zNew) xRename; -}; + int function (sqlite3_vtab *pVtab, const char *zNew) xRename; +} /** ** CAPI3REF: Virtual Table Indexing Information */ -struct sqlite3_index_info { - struct sqlite3_index_constraint { +struct sqlite3_index_info +{ + struct sqlite3_index_constraint + { int iColumn; /** Column on left-hand side of constraint */ char op; /** Constraint operator */ char usable; /** True if this constraint is usable */ int iTermOffset; /** Used internally - xBestIndex should ignore */ - }; - struct sqlite3_index_orderby { + } + struct sqlite3_index_orderby + { int iColumn; /** Column number */ char desc; /** True for DESC. False for ASC. */ - }; - struct sqlite3_index_constraint_usage { + } + struct sqlite3_index_constraint_usage + { int argvIndex; /** if >0, constraint is part of argv to xFilter */ char omit; /** Do not code a test for this constraint */ - }; - /* Inputs */ - int nConstraint; /** Number of entries in aConstraint */ - sqlite3_index_constraint* aConstraint; /** Table of WHERE clause constraints */ - int nOrderBy; /** Number of terms in the ORDER BY clause */ - sqlite3_index_orderby *aOrderBy; /** The ORDER BY clause */ - /* Outputs */ - sqlite3_index_constraint_usage *aConstraintUsage; - int idxNum; /** Number used to identify the index */ - char *idxStr; /** String, possibly obtained from sqlite3_malloc */ - int needToFreeIdxStr; /** Free idxStr using sqlite3_free() if true */ - int orderByConsumed; /** True if output is already ordered */ - double estimatedCost; /** Estimated cost of using this index */ -}; + } + /* Inputs */ + int nConstraint; /** Number of entries in aConstraint */ + sqlite3_index_constraint* aConstraint; /** Table of WHERE clause constraints */ + int nOrderBy; /** Number of terms in the ORDER BY clause */ + sqlite3_index_orderby *aOrderBy; /** The ORDER BY clause */ + /* Outputs */ + sqlite3_index_constraint_usage *aConstraintUsage; + int idxNum; /** Number used to identify the index */ + char *idxStr; /** String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /** Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /** True if output is already ordered */ + double estimatedCost; /** Estimated cost of using this index */ +} /** ** CAPI3REF: Virtual Table Constraint Operator Codes */ enum - SQLITE_INDEX_CONSTRAINT_EQ = 2, - SQLITE_INDEX_CONSTRAINT_GT = 4, - SQLITE_INDEX_CONSTRAINT_LE = 8, - SQLITE_INDEX_CONSTRAINT_LT = 16, - SQLITE_INDEX_CONSTRAINT_GE = 32, - SQLITE_INDEX_CONSTRAINT_MATCH = 64; +{ + SQLITE_INDEX_CONSTRAINT_EQ = 2, + SQLITE_INDEX_CONSTRAINT_GT = 4, + SQLITE_INDEX_CONSTRAINT_LE = 8, + SQLITE_INDEX_CONSTRAINT_LT = 16, + SQLITE_INDEX_CONSTRAINT_GE = 32, + SQLITE_INDEX_CONSTRAINT_MATCH = 64 +} /** ** CAPI3REF: Register A Virtual Table Implementation */ int sqlite3_create_module( - sqlite3 *db, /* SQLite connection to register module with */ - const(char)*zName, /* Name of the module */ - const(sqlite3_module)*p, /* Methods for the module */ - void *pClientData /* Client data for xCreate/xConnect */ + sqlite3 *db, /* SQLite connection to register module with */ + const(char)*zName, /* Name of the module */ + const(sqlite3_module)*p, /* Methods for the module */ + void *pClientData /* Client data for xCreate/xConnect */ ); /// Ditto int sqlite3_create_module_v2( - sqlite3 *db, /* SQLite connection to register module with */ - const(char)*zName, /* Name of the module */ - const(sqlite3_module)*p, /* Methods for the module */ - void *pClientData, /* Client data for xCreate/xConnect */ - void function (void*) xDestroy /* Module destructor function */ + sqlite3 *db, /* SQLite connection to register module with */ + const(char)*zName, /* Name of the module */ + const(sqlite3_module)*p, /* Methods for the module */ + void *pClientData, /* Client data for xCreate/xConnect */ + void function (void*) xDestroy /* Module destructor function */ ); /** ** CAPI3REF: Virtual Table Instance Object */ -struct sqlite3_vtab { - const(sqlite3_module)*pModule; /** The module for this virtual table */ - int nRef; /** NO LONGER USED */ - char *zErrMsg; /** Error message from sqlite3_mprintf() */ - /* Virtual table implementations will typically add additional fields */ -}; +struct sqlite3_vtab +{ + const(sqlite3_module)*pModule; /** The module for this virtual table */ + int nRef; /** NO LONGER USED */ + char *zErrMsg; /** Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +} /** ** CAPI3REF: Virtual Table Cursor Object */ -struct sqlite3_vtab_cursor { - sqlite3_vtab *pVtab; /** Virtual table of this cursor */ - /* Virtual table implementations will typically add additional fields */ -}; +struct sqlite3_vtab_cursor +{ + sqlite3_vtab *pVtab; /** Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +} /** ** CAPI3REF: Declare The Schema Of A Virtual Table @@ -1279,13 +1444,13 @@ struct sqlite3_blob; ** CAPI3REF: Open A BLOB For Incremental I/O */ int sqlite3_blob_open( - sqlite3*, - const(char)*zDb, - const(char)*zTable, - const(char)*zColumn, - sqlite3_int64 iRow, - int flags, - sqlite3_blob **ppBlob + sqlite3*, + const(char)* zDb, + const(char)* zTable, + const(char)* zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob ); /** @@ -1338,17 +1503,18 @@ void sqlite3_mutex_leave(sqlite3_mutex*); /** ** CAPI3REF: Mutex Methods Object */ -struct sqlite3_mutex_methods { - int function () xMutexInit; - int function () xMutexEnd; - sqlite3_mutex* function (int) xMutexAlloc; - void function (sqlite3_mutex *) xMutexFree; - void function (sqlite3_mutex *) xMutexEnter; - int function (sqlite3_mutex *) xMutexTry; - void function (sqlite3_mutex *) xMutexLeave; - int function (sqlite3_mutex *) xMutexHeld; - int function (sqlite3_mutex *) xMutexNotheld; -}; +struct sqlite3_mutex_methods +{ + int function () xMutexInit; + int function () xMutexEnd; + sqlite3_mutex* function (int) xMutexAlloc; + void function (sqlite3_mutex *) xMutexFree; + void function (sqlite3_mutex *) xMutexEnter; + int function (sqlite3_mutex *) xMutexTry; + void function (sqlite3_mutex *) xMutexLeave; + int function (sqlite3_mutex *) xMutexHeld; + int function (sqlite3_mutex *) xMutexNotheld; +} /** ** CAPI3REF: Mutex Verification Routines @@ -1364,18 +1530,25 @@ int sqlite3_mutex_notheld(sqlite3_mutex*); ** CAPI3REF: Mutex Types */ enum - SQLITE_MUTEX_FAST = 0, - SQLITE_MUTEX_RECURSIVE = 1, - SQLITE_MUTEX_STATIC_MASTER = 2; +{ + SQLITE_MUTEX_FAST = 0, + SQLITE_MUTEX_RECURSIVE = 1, + SQLITE_MUTEX_STATIC_MASTER = 2 +} /// Ditto enum - SQLITE_MUTEX_STATIC_MEM = 3, /** sqlite3_malloc() */ - SQLITE_MUTEX_STATIC_MEM2 = 4, /** NOT USED */ - SQLITE_MUTEX_STATIC_OPEN = 4, /** sqlite3BtreeOpen() */ - SQLITE_MUTEX_STATIC_PRNG = 5, /** sqlite3_random() */ - SQLITE_MUTEX_STATIC_LRU = 6, /** lru page list */ - SQLITE_MUTEX_STATIC_LRU2 = 7, /** NOT USED */ - SQLITE_MUTEX_STATIC_PMEM = 7; +{ + SQLITE_MUTEX_STATIC_MEM = 3, /** sqlite3_malloc() */ + SQLITE_MUTEX_STATIC_MEM2 = 4, /** NOT USED */ + SQLITE_MUTEX_STATIC_OPEN = 4, /** sqlite3BtreeOpen() */ + SQLITE_MUTEX_STATIC_PRNG = 5, /** sqlite3_random() */ + SQLITE_MUTEX_STATIC_LRU = 6, /** lru page list */ + SQLITE_MUTEX_STATIC_LRU2 = 7, /** NOT USED */ + SQLITE_MUTEX_STATIC_PMEM = 7, + SQLITE_MUTEX_STATIC_APP1 = 8, + SQLITE_MUTEX_STATIC_APP2 = 9, + SQLITE_MUTEX_STATIC_APP3 = 10 +} /** ** CAPI3REF: Retrieve the mutex for a database connection @@ -1396,22 +1569,29 @@ int sqlite3_test_control(int op, ...); ** CAPI3REF: Testing Interface Operation Codes */ enum - SQLITE_TESTCTRL_FIRST = 5, - SQLITE_TESTCTRL_PRNG_SAVE = 5, - SQLITE_TESTCTRL_PRNG_RESTORE = 6, - SQLITE_TESTCTRL_PRNG_RESET = 7, - SQLITE_TESTCTRL_BITVEC_TEST = 8, - SQLITE_TESTCTRL_FAULT_INSTALL = 9, - SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS = 10, - SQLITE_TESTCTRL_PENDING_BYTE = 11, - SQLITE_TESTCTRL_ASSERT = 12, - SQLITE_TESTCTRL_ALWAYS = 13, - SQLITE_TESTCTRL_RESERVE = 14, - SQLITE_TESTCTRL_OPTIMIZATIONS = 15, - SQLITE_TESTCTRL_ISKEYWORD = 16, - SQLITE_TESTCTRL_PGHDRSZ = 17, - SQLITE_TESTCTRL_SCRATCHMALLOC = 18, - SQLITE_TESTCTRL_LAST = 18; +{ + SQLITE_TESTCTRL_FIRST = 5, + SQLITE_TESTCTRL_PRNG_SAVE = 5, + SQLITE_TESTCTRL_PRNG_RESTORE = 6, + SQLITE_TESTCTRL_PRNG_RESET = 7, + SQLITE_TESTCTRL_BITVEC_TEST = 8, + SQLITE_TESTCTRL_FAULT_INSTALL = 9, + SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS = 10, + SQLITE_TESTCTRL_PENDING_BYTE = 11, + SQLITE_TESTCTRL_ASSERT = 12, + SQLITE_TESTCTRL_ALWAYS = 13, + SQLITE_TESTCTRL_RESERVE = 14, + SQLITE_TESTCTRL_OPTIMIZATIONS = 15, + SQLITE_TESTCTRL_ISKEYWORD = 16, + SQLITE_TESTCTRL_PGHDRSZ = 17, + SQLITE_TESTCTRL_SCRATCHMALLOC = 18, + SQLITE_TESTCTRL_EXPLAIN_STMT = 19, + SQLITE_TESTCTRL_NEVER_CORRUPT = 20, + SQLITE_TESTCTRL_VDBE_COVERAGE = 21, + SQLITE_TESTCTRL_BYTEORDER = 22, + SQLITE_TESTCTRL_ISINIT = 23, + SQLITE_TESTCTRL_LAST = 23 +} /** ** CAPI3REF: SQLite Runtime Status @@ -1423,16 +1603,18 @@ int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); ** CAPI3REF: Status Parameters */ enum - SQLITE_STATUS_MEMORY_USED = 0, - SQLITE_STATUS_PAGECACHE_USED = 1, - SQLITE_STATUS_PAGECACHE_OVERFLOW = 2, - SQLITE_STATUS_SCRATCH_USED = 3, - SQLITE_STATUS_SCRATCH_OVERFLOW = 4, - SQLITE_STATUS_MALLOC_SIZE = 5, - SQLITE_STATUS_PARSER_STACK = 6, - SQLITE_STATUS_PAGECACHE_SIZE = 7, - SQLITE_STATUS_SCRATCH_SIZE = 8, - SQLITE_STATUS_MALLOC_COUNT = 9; +{ + SQLITE_STATUS_MEMORY_USED = 0, + SQLITE_STATUS_PAGECACHE_USED = 1, + SQLITE_STATUS_PAGECACHE_OVERFLOW = 2, + SQLITE_STATUS_SCRATCH_USED = 3, + SQLITE_STATUS_SCRATCH_OVERFLOW = 4, + SQLITE_STATUS_MALLOC_SIZE = 5, + SQLITE_STATUS_PARSER_STACK = 6, + SQLITE_STATUS_PAGECACHE_SIZE = 7, + SQLITE_STATUS_SCRATCH_SIZE = 8, + SQLITE_STATUS_MALLOC_COUNT = 9 +} /** ** CAPI3REF: Database Connection Status @@ -1443,17 +1625,20 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); ** CAPI3REF: Status Parameters for database connections */ enum - SQLITE_DBSTATUS_LOOKASIDE_USED = 0, - SQLITE_DBSTATUS_CACHE_USED = 1, - SQLITE_DBSTATUS_SCHEMA_USED = 2, - SQLITE_DBSTATUS_STMT_USED = 3, - SQLITE_DBSTATUS_LOOKASIDE_HIT = 4, - SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5, - SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6; -/// -enum - SQLITE_DBSTATUS_MAX = 6; /** Largest defined DBSTATUS */ - +{ + SQLITE_DBSTATUS_LOOKASIDE_USED = 0, + SQLITE_DBSTATUS_CACHE_USED = 1, + SQLITE_DBSTATUS_SCHEMA_USED = 2, + SQLITE_DBSTATUS_STMT_USED = 3, + SQLITE_DBSTATUS_LOOKASIDE_HIT = 4, + SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5, + SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6, + SQLITE_DBSTATUS_CACHE_HIT = 7, + SQLITE_DBSTATUS_CACHE_MISS = 8, + SQLITE_DBSTATUS_CACHE_WRITE = 9, + SQLITE_DBSTATUS_DEFERRED_FKS = 10, + SQLITE_DBSTATUS_MAX = 10 /** Largest defined DBSTATUS */ +} /** ** CAPI3REF: Prepared Statement Status @@ -1464,30 +1649,61 @@ int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** CAPI3REF: Status Parameters for prepared statements */ enum - SQLITE_STMTSTATUS_FULLSCAN_STEP = 1, - SQLITE_STMTSTATUS_SORT = 2, - SQLITE_STMTSTATUS_AUTOINDEX = 3; +{ + SQLITE_STMTSTATUS_FULLSCAN_STEP = 1, + SQLITE_STMTSTATUS_SORT = 2, + SQLITE_STMTSTATUS_AUTOINDEX = 3, + SQLITE_STMTSTATUS_VM_STEP = 4 +} /** ** CAPI3REF: Custom Page Cache Object */ struct sqlite3_pcache; +/** +** CAPI3REF: Custom Page Cache Object +*/ +struct sqlite3_pcache_page +{ + void *pBuf; /* The content of the page */ + void *pExtra; /* Extra information associated with the page */ +} + /** ** CAPI3REF: Application Defined Page Cache. */ -struct sqlite3_pcache_methods { - void *pArg; - int function (void*) xInit; - void function (void*) xShutdown; - sqlite3_pcache* function (int szPage, int bPurgeable) xCreate; - void function (sqlite3_pcache*, int nCachesize) xCachesize; - int function (sqlite3_pcache*) xPagecount; - void* function (sqlite3_pcache*, uint key, int createFlag) xFetch; - void function (sqlite3_pcache*, void*, int discard) xUnpin; - void function (sqlite3_pcache*, void*, uint oldKey, uint newKey) xRekey; - void function (sqlite3_pcache*, uint iLimit) xTruncate; - void function (sqlite3_pcache*) xDestroy; +struct sqlite3_pcache_methods2 +{ + int iVersion; + void *pArg; + int function(void*) xInit; + void function(void*) xShutdown; + sqlite3_pcache * function(int szPage, int szExtra, int bPurgeable) xCreate; + void function(sqlite3_pcache*, int nCachesize) xCachesize; + int function(sqlite3_pcache*) xPagecount; + sqlite3_pcache_page * function(sqlite3_pcache*, uint key, int createFlag) xFetch; + void function(sqlite3_pcache*, sqlite3_pcache_page*, int discard) xUnpin; + void function(sqlite3_pcache*, sqlite3_pcache_page*, + uint oldKey, uint newKey) xRekey; + void function(sqlite3_pcache*, uint iLimit) xTruncate; + void function(sqlite3_pcache*) xDestroy; + void function(sqlite3_pcache*) xShrink; +} + +struct sqlite3_pcache_methods +{ + void *pArg; + int function (void*) xInit; + void function (void*) xShutdown; + sqlite3_pcache* function (int szPage, int bPurgeable) xCreate; + void function (sqlite3_pcache*, int nCachesize) xCachesize; + int function (sqlite3_pcache*) xPagecount; + void* function (sqlite3_pcache*, uint key, int createFlag) xFetch; + void function (sqlite3_pcache*, void*, int discard) xUnpin; + void function (sqlite3_pcache*, void*, uint oldKey, uint newKey) xRekey; + void function (sqlite3_pcache*, uint iLimit) xTruncate; + void function (sqlite3_pcache*) xDestroy; }; /** @@ -1499,10 +1715,10 @@ struct sqlite3_backup; ** CAPI3REF: Online Backup API. */ sqlite3_backup *sqlite3_backup_init( - sqlite3 *pDest, /** Destination database handle */ - const(char)*zDestName, /** Destination database name */ - sqlite3 *pSource, /** Source database handle */ - const(char)*zSourceName /** Source database name */ + sqlite3 *pDest, /** Destination database handle */ + const(char)*zDestName, /** Destination database name */ + sqlite3 *pSource, /** Source database handle */ + const(char)*zSourceName /** Source database name */ ); /// Ditto int sqlite3_backup_step(sqlite3_backup *p, int nPage); @@ -1517,16 +1733,22 @@ int sqlite3_backup_pagecount(sqlite3_backup *p); ** CAPI3REF: Unlock Notification */ int sqlite3_unlock_notify( - sqlite3 *pBlocked, /** Waiting connection */ - void function (void **apArg, int nArg) xNotify, /** Callback function to invoke */ - void *pNotifyArg /** Argument to pass to xNotify */ + sqlite3 *pBlocked, /** Waiting connection */ + void function (void **apArg, int nArg) xNotify, /** Callback function to invoke */ + void *pNotifyArg /** Argument to pass to xNotify */ ); - /** ** CAPI3REF: String Comparison */ -int sqlite3_strnicmp(const char *, const char *, int); +int sqlite3_stricmp(const char * , const char * ); +int sqlite3_strnicmp(const char * , const char * , int); + +/* +** CAPI3REF: String Globbing +* +*/ +int sqlite3_strglob(const(char)* zGlob, const(char)* zStr); /** ** CAPI3REF: Error Logging Interface @@ -1537,9 +1759,9 @@ void sqlite3_log(int iErrCode, const char *zFormat, ...); ** CAPI3REF: Write-Ahead Log Commit Hook */ void *sqlite3_wal_hook( - sqlite3*, - int function (void *,sqlite3*,const char*,int), - void* + sqlite3*, + int function (void *,sqlite3*,const char*,int), + void* ); /** @@ -1556,20 +1778,27 @@ int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** CAPI3REF: Checkpoint a database */ int sqlite3_wal_checkpoint_v2( - sqlite3 *db, /** Database handle */ - const(char)*zDb, /** Name of attached database (or NULL) */ - int eMode, /** SQLITE_CHECKPOINT_* value */ - int *pnLog, /** OUT: Size of WAL log in frames */ - int *pnCkpt /** OUT: Total number of frames checkpointed */ + sqlite3 *db, /** Database handle */ + const(char)*zDb, /** Name of attached database (or NULL) */ + int eMode, /** SQLITE_CHECKPOINT_* value */ + int *pnLog, /** OUT: Size of WAL log in frames */ + int *pnCkpt /** OUT: Total number of frames checkpointed */ ); /** ** CAPI3REF: Checkpoint operation parameters */ enum - SQLITE_CHECKPOINT_PASSIVE = 0, - SQLITE_CHECKPOINT_FULL = 1, - SQLITE_CHECKPOINT_RESTART = 2; +{ + SQLITE_CHECKPOINT_PASSIVE = 0, + SQLITE_CHECKPOINT_FULL = 1, + SQLITE_CHECKPOINT_RESTART = 2 +} + +/* +** CAPI3REF: Virtual Table Interface Configuration +*/ +int sqlite3_vtab_config(sqlite3*, int op, ...); } /* End of the 'extern (C) block */ @@ -1591,6 +1820,20 @@ enum extern (C) { +/* +** CAPI3REF: Determine The Virtual Table Conflict Policy +*/ +int sqlite3_vtab_on_conflict(sqlite3 *); + +/* +** CAPI3REF: Conflict resolution modes +*/ +enum +{ + SQLITE_ROLLBACK = 1, + SQLITE_FAIL = 3, + SQLITE_REPLACE = 5 +} /** ** Register a geometry callback named zGeom that can be used as part of an @@ -1599,26 +1842,56 @@ extern (C) { ** SELECT ... FROM WHERE MATCH $zGeom(... params ...) */ int sqlite3_rtree_geometry_callback( - sqlite3 *db, - const(char)*zGeom, - int function (sqlite3_rtree_geometry *, int nCoord, double *aCoord, int *pRes) xGeom, - void *pContext + sqlite3 *db, + const(char)*zGeom, + int function (sqlite3_rtree_geometry *, int nCoord, double *aCoord, int *pRes) xGeom, + void *pContext ); - /** ** A pointer to a structure of the following type is passed as the first ** argument to callbacks registered using rtree_geometry_callback(). */ -struct sqlite3_rtree_geometry { - void *pContext; /** Copy of pContext passed to s_r_g_c() */ - int nParam; /** Size of array aParam[] */ - double *aParam; /** Parameters passed to SQL geom function */ - void *pUser; /** Callback implementation user data */ - void function (void *) xDelUser; /** Called by SQLite to clean up pUser */ -}; +struct sqlite3_rtree_geometry +{ + void *pContext; /** Copy of pContext passed to s_r_g_c() */ + int nParam; /** Size of array aParam[] */ + double *aParam; /** Parameters passed to SQL geom function */ + void *pUser; /** Callback implementation user data */ + void function (void *) xDelUser; /** Called by SQLite to clean up pUser */ +} + +int sqlite3_rtree_query_callback( + sqlite3 *db, + const(char)* zQueryFunc, + int function(sqlite3_rtree_query_info*) xQueryFunc, + void *pContext, + void function(void*) xDestructor +); +struct sqlite3_rtree_query_info +{ + void *pContext; /* pContext from when function registered */ + int nParam; /* Number of function parameters */ + double*aParam; /* value of function parameters */ + void *pUser; /* callback can use this, if desired */ + void function(void*) xDelUser; /* function to free pUser */ + double*aCoord; /* Coordinates of node or entry to check */ + uint *anQueue; /* Number of pending entries in the queue */ + int nCoord; /* Number of coordinates */ + int iLevel; /* Level of current node or entry */ + int mxLevel; /* The largest iLevel value in the tree */ + sqlite3_int64 iRowid; /* Rowid for current entry */ + double rParentScore; /* Score of parent node */ + int eParentWithin; /* Visibility of parent node */ + int eWithin; /* OUT: Visiblity */ + double rScore; /* OUT: Write the score here */ +} +enum +{ + NOT_WITHIN = 0, + PARTLY_WITHIN = 1, + FULLY_WITHIN = 2 +} } /* end of the 'extern (C) block */ - - diff --git a/etc/c/zlib.d b/etc/c/zlib.d index 65b1d923122..68cc0bef950 100644 --- a/etc/c/zlib.d +++ b/etc/c/zlib.d @@ -1,5 +1,6 @@ /* zlib.d: modified from zlib.h by Walter Bright */ /* updated from 1.2.1 to 1.2.3 by Thomas Kuehne */ +/* updated from 1.2.3 to 1.2.8 by Dmitry Atamanov */ module etc.c.zlib; @@ -37,8 +38,8 @@ import core.stdc.config; extern (C): -const char[] ZLIB_VERSION = "1.2.3"; -const ZLIB_VERNUM = 0x1230; +const char[] ZLIB_VERSION = "1.2.8"; +const ZLIB_VERNUM = 0x1280; /* The 'zlib' compression library provides in-memory compression and diff --git a/etc/c/zlib/ChangeLog b/etc/c/zlib/ChangeLog index f310bb0fcdb..f22aabaef53 100644 --- a/etc/c/zlib/ChangeLog +++ b/etc/c/zlib/ChangeLog @@ -1,12 +1,276 @@ ChangeLog file for zlib +Changes in 1.2.8 (28 Apr 2013) +- Update contrib/minizip/iowin32.c for Windows RT [Vollant] +- Do not force Z_CONST for C++ +- Clean up contrib/vstudio [Ro] +- Correct spelling error in zlib.h +- Fix mixed line endings in contrib/vstudio + +Changes in 1.2.7.3 (13 Apr 2013) +- Fix version numbers and DLL names in contrib/vstudio/*/zlib.rc + +Changes in 1.2.7.2 (13 Apr 2013) +- Change check for a four-byte type back to hexadecimal +- Fix typo in win32/Makefile.msc +- Add casts in gzwrite.c for pointer differences + +Changes in 1.2.7.1 (24 Mar 2013) +- Replace use of unsafe string functions with snprintf if available +- Avoid including stddef.h on Windows for Z_SOLO compile [Niessink] +- Fix gzgetc undefine when Z_PREFIX set [Turk] +- Eliminate use of mktemp in Makefile (not always available) +- Fix bug in 'F' mode for gzopen() +- Add inflateGetDictionary() function +- Correct comment in deflate.h +- Use _snprintf for snprintf in Microsoft C +- On Darwin, only use /usr/bin/libtool if libtool is not Apple +- Delete "--version" file if created by "ar --version" [Richard G.] +- Fix configure check for veracity of compiler error return codes +- Fix CMake compilation of static lib for MSVC2010 x64 +- Remove unused variable in infback9.c +- Fix argument checks in gzlog_compress() and gzlog_write() +- Clean up the usage of z_const and respect const usage within zlib +- Clean up examples/gzlog.[ch] comparisons of different types +- Avoid shift equal to bits in type (caused endless loop) +- Fix unintialized value bug in gzputc() introduced by const patches +- Fix memory allocation error in examples/zran.c [Nor] +- Fix bug where gzopen(), gzclose() would write an empty file +- Fix bug in gzclose() when gzwrite() runs out of memory +- Check for input buffer malloc failure in examples/gzappend.c +- Add note to contrib/blast to use binary mode in stdio +- Fix comparisons of differently signed integers in contrib/blast +- Check for invalid code length codes in contrib/puff +- Fix serious but very rare decompression bug in inftrees.c +- Update inflateBack() comments, since inflate() can be faster +- Use underscored I/O function names for WINAPI_FAMILY +- Add _tr_flush_bits to the external symbols prefixed by --zprefix +- Add contrib/vstudio/vc10 pre-build step for static only +- Quote --version-script argument in CMakeLists.txt +- Don't specify --version-script on Apple platforms in CMakeLists.txt +- Fix casting error in contrib/testzlib/testzlib.c +- Fix types in contrib/minizip to match result of get_crc_table() +- Simplify contrib/vstudio/vc10 with 'd' suffix +- Add TOP support to win32/Makefile.msc +- Suport i686 and amd64 assembler builds in CMakeLists.txt +- Fix typos in the use of _LARGEFILE64_SOURCE in zconf.h +- Add vc11 and vc12 build files to contrib/vstudio +- Add gzvprintf() as an undocumented function in zlib +- Fix configure for Sun shell +- Remove runtime check in configure for four-byte integer type +- Add casts and consts to ease user conversion to C++ +- Add man pages for minizip and miniunzip +- In Makefile uninstall, don't rm if preceding cd fails +- Do not return Z_BUF_ERROR if deflateParam() has nothing to write + +Changes in 1.2.7 (2 May 2012) +- Replace use of memmove() with a simple copy for portability +- Test for existence of strerror +- Restore gzgetc_ for backward compatibility with 1.2.6 +- Fix build with non-GNU make on Solaris +- Require gcc 4.0 or later on Mac OS X to use the hidden attribute +- Include unistd.h for Watcom C +- Use __WATCOMC__ instead of __WATCOM__ +- Do not use the visibility attribute if NO_VIZ defined +- Improve the detection of no hidden visibility attribute +- Avoid using __int64 for gcc or solo compilation +- Cast to char * in gzprintf to avoid warnings [Zinser] +- Fix make_vms.com for VAX [Zinser] +- Don't use library or built-in byte swaps +- Simplify test and use of gcc hidden attribute +- Fix bug in gzclose_w() when gzwrite() fails to allocate memory +- Add "x" (O_EXCL) and "e" (O_CLOEXEC) modes support to gzopen() +- Fix bug in test/minigzip.c for configure --solo +- Fix contrib/vstudio project link errors [Mohanathas] +- Add ability to choose the builder in make_vms.com [Schweda] +- Add DESTDIR support to mingw32 win32/Makefile.gcc +- Fix comments in win32/Makefile.gcc for proper usage +- Allow overriding the default install locations for cmake +- Generate and install the pkg-config file with cmake +- Build both a static and a shared version of zlib with cmake +- Include version symbols for cmake builds +- If using cmake with MSVC, add the source directory to the includes +- Remove unneeded EXTRA_CFLAGS from win32/Makefile.gcc [Truta] +- Move obsolete emx makefile to old [Truta] +- Allow the use of -Wundef when compiling or using zlib +- Avoid the use of the -u option with mktemp +- Improve inflate() documentation on the use of Z_FINISH +- Recognize clang as gcc +- Add gzopen_w() in Windows for wide character path names +- Rename zconf.h in CMakeLists.txt to move it out of the way +- Add source directory in CMakeLists.txt for building examples +- Look in build directory for zlib.pc in CMakeLists.txt +- Remove gzflags from zlibvc.def in vc9 and vc10 +- Fix contrib/minizip compilation in the MinGW environment +- Update ./configure for Solaris, support --64 [Mooney] +- Remove -R. from Solaris shared build (possible security issue) +- Avoid race condition for parallel make (-j) running example +- Fix type mismatch between get_crc_table() and crc_table +- Fix parsing of version with "-" in CMakeLists.txt [Snider, Ziegler] +- Fix the path to zlib.map in CMakeLists.txt +- Force the native libtool in Mac OS X to avoid GNU libtool [Beebe] +- Add instructions to win32/Makefile.gcc for shared install [Torri] + +Changes in 1.2.6.1 (12 Feb 2012) +- Avoid the use of the Objective-C reserved name "id" +- Include io.h in gzguts.h for Microsoft compilers +- Fix problem with ./configure --prefix and gzgetc macro +- Include gz_header definition when compiling zlib solo +- Put gzflags() functionality back in zutil.c +- Avoid library header include in crc32.c for Z_SOLO +- Use name in GCC_CLASSIC as C compiler for coverage testing, if set +- Minor cleanup in contrib/minizip/zip.c [Vollant] +- Update make_vms.com [Zinser] +- Remove unnecessary gzgetc_ function +- Use optimized byte swap operations for Microsoft and GNU [Snyder] +- Fix minor typo in zlib.h comments [Rzesniowiecki] + +Changes in 1.2.6 (29 Jan 2012) +- Update the Pascal interface in contrib/pascal +- Fix function numbers for gzgetc_ in zlibvc.def files +- Fix configure.ac for contrib/minizip [Schiffer] +- Fix large-entry detection in minizip on 64-bit systems [Schiffer] +- Have ./configure use the compiler return code for error indication +- Fix CMakeLists.txt for cross compilation [McClure] +- Fix contrib/minizip/zip.c for 64-bit architectures [Dalsnes] +- Fix compilation of contrib/minizip on FreeBSD [Marquez] +- Correct suggested usages in win32/Makefile.msc [Shachar, Horvath] +- Include io.h for Turbo C / Borland C on all platforms [Truta] +- Make version explicit in contrib/minizip/configure.ac [Bosmans] +- Avoid warning for no encryption in contrib/minizip/zip.c [Vollant] +- Minor cleanup up contrib/minizip/unzip.c [Vollant] +- Fix bug when compiling minizip with C++ [Vollant] +- Protect for long name and extra fields in contrib/minizip [Vollant] +- Avoid some warnings in contrib/minizip [Vollant] +- Add -I../.. -L../.. to CFLAGS for minizip and miniunzip +- Add missing libs to minizip linker command +- Add support for VPATH builds in contrib/minizip +- Add an --enable-demos option to contrib/minizip/configure +- Add the generation of configure.log by ./configure +- Exit when required parameters not provided to win32/Makefile.gcc +- Have gzputc return the character written instead of the argument +- Use the -m option on ldconfig for BSD systems [Tobias] +- Correct in zlib.map when deflateResetKeep was added + +Changes in 1.2.5.3 (15 Jan 2012) +- Restore gzgetc function for binary compatibility +- Do not use _lseeki64 under Borland C++ [Truta] +- Update win32/Makefile.msc to build test/*.c [Truta] +- Remove old/visualc6 given CMakefile and other alternatives +- Update AS400 build files and documentation [Monnerat] +- Update win32/Makefile.gcc to build test/*.c [Truta] +- Permit stronger flushes after Z_BLOCK flushes +- Avoid extraneous empty blocks when doing empty flushes +- Permit Z_NULL arguments to deflatePending +- Allow deflatePrime() to insert bits in the middle of a stream +- Remove second empty static block for Z_PARTIAL_FLUSH +- Write out all of the available bits when using Z_BLOCK +- Insert the first two strings in the hash table after a flush + +Changes in 1.2.5.2 (17 Dec 2011) +- fix ld error: unable to find version dependency 'ZLIB_1.2.5' +- use relative symlinks for shared libs +- Avoid searching past window for Z_RLE strategy +- Assure that high-water mark initialization is always applied in deflate +- Add assertions to fill_window() in deflate.c to match comments +- Update python link in README +- Correct spelling error in gzread.c +- Fix bug in gzgets() for a concatenated empty gzip stream +- Correct error in comment for gz_make() +- Change gzread() and related to ignore junk after gzip streams +- Allow gzread() and related to continue after gzclearerr() +- Allow gzrewind() and gzseek() after a premature end-of-file +- Simplify gzseek() now that raw after gzip is ignored +- Change gzgetc() to a macro for speed (~40% speedup in testing) +- Fix gzclose() to return the actual error last encountered +- Always add large file support for windows +- Include zconf.h for windows large file support +- Include zconf.h.cmakein for windows large file support +- Update zconf.h.cmakein on make distclean +- Merge vestigial vsnprintf determination from zutil.h to gzguts.h +- Clarify how gzopen() appends in zlib.h comments +- Correct documentation of gzdirect() since junk at end now ignored +- Add a transparent write mode to gzopen() when 'T' is in the mode +- Update python link in zlib man page +- Get inffixed.h and MAKEFIXED result to match +- Add a ./config --solo option to make zlib subset with no libary use +- Add undocumented inflateResetKeep() function for CAB file decoding +- Add --cover option to ./configure for gcc coverage testing +- Add #define ZLIB_CONST option to use const in the z_stream interface +- Add comment to gzdopen() in zlib.h to use dup() when using fileno() +- Note behavior of uncompress() to provide as much data as it can +- Add files in contrib/minizip to aid in building libminizip +- Split off AR options in Makefile.in and configure +- Change ON macro to Z_ARG to avoid application conflicts +- Facilitate compilation with Borland C++ for pragmas and vsnprintf +- Include io.h for Turbo C / Borland C++ +- Move example.c and minigzip.c to test/ +- Simplify incomplete code table filling in inflate_table() +- Remove code from inflate.c and infback.c that is impossible to execute +- Test the inflate code with full coverage +- Allow deflateSetDictionary, inflateSetDictionary at any time (in raw) +- Add deflateResetKeep and fix inflateResetKeep to retain dictionary +- Fix gzwrite.c to accommodate reduced memory zlib compilation +- Have inflate() with Z_FINISH avoid the allocation of a window +- Do not set strm->adler when doing raw inflate +- Fix gzeof() to behave just like feof() when read is not past end of file +- Fix bug in gzread.c when end-of-file is reached +- Avoid use of Z_BUF_ERROR in gz* functions except for premature EOF +- Document gzread() capability to read concurrently written files +- Remove hard-coding of resource compiler in CMakeLists.txt [Blammo] + +Changes in 1.2.5.1 (10 Sep 2011) +- Update FAQ entry on shared builds (#13) +- Avoid symbolic argument to chmod in Makefile.in +- Fix bug and add consts in contrib/puff [Oberhumer] +- Update contrib/puff/zeros.raw test file to have all block types +- Add full coverage test for puff in contrib/puff/Makefile +- Fix static-only-build install in Makefile.in +- Fix bug in unzGetCurrentFileInfo() in contrib/minizip [Kuno] +- Add libz.a dependency to shared in Makefile.in for parallel builds +- Spell out "number" (instead of "nb") in zlib.h for total_in, total_out +- Replace $(...) with `...` in configure for non-bash sh [Bowler] +- Add darwin* to Darwin* and solaris* to SunOS\ 5* in configure [Groffen] +- Add solaris* to Linux* in configure to allow gcc use [Groffen] +- Add *bsd* to Linux* case in configure [Bar-Lev] +- Add inffast.obj to dependencies in win32/Makefile.msc +- Correct spelling error in deflate.h [Kohler] +- Change libzdll.a again to libz.dll.a (!) in win32/Makefile.gcc +- Add test to configure for GNU C looking for gcc in output of $cc -v +- Add zlib.pc generation to win32/Makefile.gcc [Weigelt] +- Fix bug in zlib.h for _FILE_OFFSET_BITS set and _LARGEFILE64_SOURCE not +- Add comment in zlib.h that adler32_combine with len2 < 0 makes no sense +- Make NO_DIVIDE option in adler32.c much faster (thanks to John Reiser) +- Make stronger test in zconf.h to include unistd.h for LFS +- Apply Darwin patches for 64-bit file offsets to contrib/minizip [Slack] +- Fix zlib.h LFS support when Z_PREFIX used +- Add updated as400 support (removed from old) [Monnerat] +- Avoid deflate sensitivity to volatile input data +- Avoid division in adler32_combine for NO_DIVIDE +- Clarify the use of Z_FINISH with deflateBound() amount of space +- Set binary for output file in puff.c +- Use u4 type for crc_table to avoid conversion warnings +- Apply casts in zlib.h to avoid conversion warnings +- Add OF to prototypes for adler32_combine_ and crc32_combine_ [Miller] +- Improve inflateSync() documentation to note indeterminancy +- Add deflatePending() function to return the amount of pending output +- Correct the spelling of "specification" in FAQ [Randers-Pehrson] +- Add a check in configure for stdarg.h, use for gzprintf() +- Check that pointers fit in ints when gzprint() compiled old style +- Add dummy name before $(SHAREDLIBV) in Makefile [Bar-Lev, Bowler] +- Delete line in configure that adds -L. libz.a to LDFLAGS [Weigelt] +- Add debug records in assmebler code [Londer] +- Update RFC references to use http://tools.ietf.org/html/... [Li] +- Add --archs option, use of libtool to configure for Mac OS X [Borstel] + Changes in 1.2.5 (19 Apr 2010) - Disable visibility attribute in win32/Makefile.gcc [Bar-Lev] - Default to libdir as sharedlibdir in configure [Nieder] - Update copyright dates on modified source files - Update trees.c to be able to generate modified trees.h - Exit configure for MinGW, suggesting win32/Makefile.gcc +- Check for NULL path in gz_open [Homurlu] Changes in 1.2.4.5 (18 Apr 2010) - Set sharedlibdir in configure [Torok] @@ -261,7 +525,7 @@ Changes in 1.2.3.4 (21 Dec 2009) - Clear bytes after deflate lookahead to avoid use of uninitialized data - Change a limit in inftrees.c to be more transparent to Coverity Prevent - Update win32/zlib.def with exported symbols from zlib.h -- Correct spelling error in zlib.h [Willem] +- Correct spelling errors in zlib.h [Willem, Sobrado] - Allow Z_BLOCK for deflate() to force a new block - Allow negative bits in inflatePrime() to delete existing bit buffer - Add Z_TREES flush option to inflate() to return at end of trees diff --git a/etc/c/zlib/README b/etc/c/zlib/README index d4219bf889f..5ca9d127eda 100644 --- a/etc/c/zlib/README +++ b/etc/c/zlib/README @@ -1,22 +1,22 @@ ZLIB DATA COMPRESSION LIBRARY -zlib 1.2.5 is a general purpose data compression library. All the code is +zlib 1.2.8 is a general purpose data compression library. All the code is thread safe. The data format used by the zlib library is described by RFCs (Request for Comments) 1950 to 1952 in the files -http://www.ietf.org/rfc/rfc1950.txt (zlib format), rfc1951.txt (deflate format) -and rfc1952.txt (gzip format). +http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and +rfc1952 (gzip format). All functions of the compression library are documented in the file zlib.h (volunteer to write man pages welcome, contact zlib@gzip.org). A usage example -of the library is given in the file example.c which also tests that the library -is working correctly. Another example is given in the file minigzip.c. The -compression library itself is composed of all source files except example.c and -minigzip.c. +of the library is given in the file test/example.c which also tests that +the library is working correctly. Another example is given in the file +test/minigzip.c. The compression library itself is composed of all source +files in the root directory. To compile all files and run the test program, follow the instructions given at the top of Makefile.in. In short "./configure; make test", and if that goes -well, "make install" should work for most flavors of Unix. For Windows, use one -of the special makefiles in win32/ or contrib/vstudio/ . For VMS, use +well, "make install" should work for most flavors of Unix. For Windows, use +one of the special makefiles in win32/ or contrib/vstudio/ . For VMS, use make_vms.com. Questions about zlib should be sent to , or to Gilles Vollant @@ -31,7 +31,7 @@ Mark Nelson wrote an article about zlib for the Jan. 1997 issue of Dr. Dobb's Journal; a copy of the article is available at http://marknelson.us/1997/01/01/zlib-engine/ . -The changes made in version 1.2.5 are documented in the file ChangeLog. +The changes made in version 1.2.8 are documented in the file ChangeLog. Unsupported third party contributions are provided in directory contrib/ . @@ -44,7 +44,7 @@ http://search.cpan.org/~pmqs/IO-Compress-Zlib/ . A Python interface to zlib written by A.M. Kuchling is available in Python 1.5 and later versions, see -http://www.python.org/doc/lib/module-zlib.html . +http://docs.python.org/library/zlib.html . zlib is built into tcl: http://wiki.tcl.tk/4610 . @@ -84,7 +84,7 @@ Acknowledgments: Copyright notice: - (C) 1995-2010 Jean-loup Gailly and Mark Adler + (C) 1995-2013 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/etc/c/zlib/adler32.c b/etc/c/zlib/adler32.c index 65ad6a5adc4..a868f073d8a 100644 --- a/etc/c/zlib/adler32.c +++ b/etc/c/zlib/adler32.c @@ -1,5 +1,5 @@ /* adler32.c -- compute the Adler-32 checksum of a data stream - * Copyright (C) 1995-2007 Mark Adler + * Copyright (C) 1995-2011 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -9,9 +9,9 @@ #define local static -local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2); +local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); -#define BASE 65521UL /* largest prime smaller than 65536 */ +#define BASE 65521 /* largest prime smaller than 65536 */ #define NMAX 5552 /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ @@ -21,39 +21,44 @@ local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2); #define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); #define DO16(buf) DO8(buf,0); DO8(buf,8); -/* use NO_DIVIDE if your processor does not do division in hardware */ +/* use NO_DIVIDE if your processor does not do division in hardware -- + try it both ways to see which is faster */ #ifdef NO_DIVIDE -# define MOD(a) \ +/* note that this assumes BASE is 65521, where 65536 % 65521 == 15 + (thank you to John Reiser for pointing this out) */ +# define CHOP(a) \ + do { \ + unsigned long tmp = a >> 16; \ + a &= 0xffffUL; \ + a += (tmp << 4) - tmp; \ + } while (0) +# define MOD28(a) \ do { \ - if (a >= (BASE << 16)) a -= (BASE << 16); \ - if (a >= (BASE << 15)) a -= (BASE << 15); \ - if (a >= (BASE << 14)) a -= (BASE << 14); \ - if (a >= (BASE << 13)) a -= (BASE << 13); \ - if (a >= (BASE << 12)) a -= (BASE << 12); \ - if (a >= (BASE << 11)) a -= (BASE << 11); \ - if (a >= (BASE << 10)) a -= (BASE << 10); \ - if (a >= (BASE << 9)) a -= (BASE << 9); \ - if (a >= (BASE << 8)) a -= (BASE << 8); \ - if (a >= (BASE << 7)) a -= (BASE << 7); \ - if (a >= (BASE << 6)) a -= (BASE << 6); \ - if (a >= (BASE << 5)) a -= (BASE << 5); \ - if (a >= (BASE << 4)) a -= (BASE << 4); \ - if (a >= (BASE << 3)) a -= (BASE << 3); \ - if (a >= (BASE << 2)) a -= (BASE << 2); \ - if (a >= (BASE << 1)) a -= (BASE << 1); \ + CHOP(a); \ if (a >= BASE) a -= BASE; \ } while (0) -# define MOD4(a) \ +# define MOD(a) \ do { \ - if (a >= (BASE << 4)) a -= (BASE << 4); \ - if (a >= (BASE << 3)) a -= (BASE << 3); \ - if (a >= (BASE << 2)) a -= (BASE << 2); \ - if (a >= (BASE << 1)) a -= (BASE << 1); \ + CHOP(a); \ + MOD28(a); \ + } while (0) +# define MOD63(a) \ + do { /* this assumes a is not negative */ \ + z_off64_t tmp = a >> 32; \ + a &= 0xffffffffL; \ + a += (tmp << 8) - (tmp << 5) + tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ if (a >= BASE) a -= BASE; \ } while (0) #else # define MOD(a) a %= BASE -# define MOD4(a) a %= BASE +# define MOD28(a) a %= BASE +# define MOD63(a) a %= BASE #endif /* ========================================================================= */ @@ -92,7 +97,7 @@ uLong ZEXPORT adler32(adler, buf, len) } if (adler >= BASE) adler -= BASE; - MOD4(sum2); /* only added so many BASE's */ + MOD28(sum2); /* only added so many BASE's */ return adler | (sum2 << 16); } @@ -137,8 +142,13 @@ local uLong adler32_combine_(adler1, adler2, len2) unsigned long sum2; unsigned rem; + /* for negative len, return invalid adler32 as a clue for debugging */ + if (len2 < 0) + return 0xffffffffUL; + /* the derivation of this formula is left as an exercise for the reader */ - rem = (unsigned)(len2 % BASE); + MOD63(len2); /* assumes len2 >= 0 */ + rem = (unsigned)len2; sum1 = adler1 & 0xffff; sum2 = rem * sum1; MOD(sum2); diff --git a/etc/c/zlib/algorithm.txt b/etc/c/zlib/algorithm.txt index 34960bddacc..c97f495020b 100644 --- a/etc/c/zlib/algorithm.txt +++ b/etc/c/zlib/algorithm.txt @@ -206,4 +206,4 @@ Compression,'' IEEE Transactions on Information Theory, Vol. 23, No. 3, pp. 337-343. ``DEFLATE Compressed Data Format Specification'' available in -http://www.ietf.org/rfc/rfc1951.txt +http://tools.ietf.org/html/rfc1951 diff --git a/etc/c/zlib/compress.c b/etc/c/zlib/compress.c index ea4dfbe9d7b..6e9762676a0 100644 --- a/etc/c/zlib/compress.c +++ b/etc/c/zlib/compress.c @@ -29,7 +29,7 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) z_stream stream; int err; - stream.next_in = (Bytef*)source; + stream.next_in = (z_const Bytef *)source; stream.avail_in = (uInt)sourceLen; #ifdef MAXSEG_64K /* Check for source > 64K on 16-bit machine: */ diff --git a/etc/c/zlib/crc32.c b/etc/c/zlib/crc32.c index 91be372d224..979a7190a3c 100644 --- a/etc/c/zlib/crc32.c +++ b/etc/c/zlib/crc32.c @@ -1,5 +1,5 @@ /* crc32.c -- compute the CRC-32 of a data stream - * Copyright (C) 1995-2006, 2010 Mark Adler + * Copyright (C) 1995-2006, 2010, 2011, 2012 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h * * Thanks to Rodney Brown for his contribution of faster @@ -17,6 +17,8 @@ of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should first call get_crc_table() to initialize the tables before allowing more than one thread to use crc32(). + + DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h. */ #ifdef MAKECRCH @@ -30,31 +32,11 @@ #define local static -/* Find a four-byte integer type for crc32_little() and crc32_big(). */ -#ifndef NOBYFOUR -# ifdef STDC /* need ANSI C limits.h to determine sizes */ -# include -# define BYFOUR -# if (UINT_MAX == 0xffffffffUL) - typedef unsigned int u4; -# else -# if (ULONG_MAX == 0xffffffffUL) - typedef unsigned long u4; -# else -# if (USHRT_MAX == 0xffffffffUL) - typedef unsigned short u4; -# else -# undef BYFOUR /* can't find a four-byte integer type! */ -# endif -# endif -# endif -# endif /* STDC */ -#endif /* !NOBYFOUR */ - /* Definitions for doing the crc four data bytes at a time. */ +#if !defined(NOBYFOUR) && defined(Z_U4) +# define BYFOUR +#endif #ifdef BYFOUR -# define REV(w) ((((w)>>24)&0xff)+(((w)>>8)&0xff00)+ \ - (((w)&0xff00)<<8)+(((w)&0xff)<<24)) local unsigned long crc32_little OF((unsigned long, const unsigned char FAR *, unsigned)); local unsigned long crc32_big OF((unsigned long, @@ -68,16 +50,16 @@ local unsigned long gf2_matrix_times OF((unsigned long *mat, unsigned long vec)); local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); -local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2); +local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2)); #ifdef DYNAMIC_CRC_TABLE local volatile int crc_table_empty = 1; -local unsigned long FAR crc_table[TBLS][256]; +local z_crc_t FAR crc_table[TBLS][256]; local void make_crc_table OF((void)); #ifdef MAKECRCH - local void write_table OF((FILE *, const unsigned long FAR *)); + local void write_table OF((FILE *, const z_crc_t FAR *)); #endif /* MAKECRCH */ /* Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: @@ -107,9 +89,9 @@ local void make_crc_table OF((void)); */ local void make_crc_table() { - unsigned long c; + z_crc_t c; int n, k; - unsigned long poly; /* polynomial exclusive-or pattern */ + z_crc_t poly; /* polynomial exclusive-or pattern */ /* terms of polynomial defining this crc (except x^32): */ static volatile int first = 1; /* flag to limit concurrent making */ static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; @@ -121,13 +103,13 @@ local void make_crc_table() first = 0; /* make exclusive-or pattern from polynomial (0xedb88320UL) */ - poly = 0UL; - for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) - poly |= 1UL << (31 - p[n]); + poly = 0; + for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++) + poly |= (z_crc_t)1 << (31 - p[n]); /* generate a crc for every 8-bit value */ for (n = 0; n < 256; n++) { - c = (unsigned long)n; + c = (z_crc_t)n; for (k = 0; k < 8; k++) c = c & 1 ? poly ^ (c >> 1) : c >> 1; crc_table[0][n] = c; @@ -138,11 +120,11 @@ local void make_crc_table() and then the byte reversal of those as well as the first table */ for (n = 0; n < 256; n++) { c = crc_table[0][n]; - crc_table[4][n] = REV(c); + crc_table[4][n] = ZSWAP32(c); for (k = 1; k < 4; k++) { c = crc_table[0][c & 0xff] ^ (c >> 8); crc_table[k][n] = c; - crc_table[k + 4][n] = REV(c); + crc_table[k + 4][n] = ZSWAP32(c); } } #endif /* BYFOUR */ @@ -164,7 +146,7 @@ local void make_crc_table() if (out == NULL) return; fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); - fprintf(out, "local const unsigned long FAR "); + fprintf(out, "local const z_crc_t FAR "); fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); write_table(out, crc_table[0]); # ifdef BYFOUR @@ -184,12 +166,13 @@ local void make_crc_table() #ifdef MAKECRCH local void write_table(out, table) FILE *out; - const unsigned long FAR *table; + const z_crc_t FAR *table; { int n; for (n = 0; n < 256; n++) - fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", + (unsigned long)(table[n]), n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); } #endif /* MAKECRCH */ @@ -204,13 +187,13 @@ local void write_table(out, table) /* ========================================================================= * This function can be used by asm versions of crc32() */ -const unsigned long FAR * ZEXPORT get_crc_table() +const z_crc_t FAR * ZEXPORT get_crc_table() { #ifdef DYNAMIC_CRC_TABLE if (crc_table_empty) make_crc_table(); #endif /* DYNAMIC_CRC_TABLE */ - return (const unsigned long FAR *)crc_table; + return (const z_crc_t FAR *)crc_table; } /* ========================================================================= */ @@ -232,7 +215,7 @@ unsigned long ZEXPORT crc32(crc, buf, len) #ifdef BYFOUR if (sizeof(void *) == sizeof(ptrdiff_t)) { - u4 endian; + z_crc_t endian; endian = 1; if (*((unsigned char *)(&endian))) @@ -266,17 +249,17 @@ local unsigned long crc32_little(crc, buf, len) const unsigned char FAR *buf; unsigned len; { - register u4 c; - register const u4 FAR *buf4; + register z_crc_t c; + register const z_crc_t FAR *buf4; - c = (u4)crc; + c = (z_crc_t)crc; c = ~c; while (len && ((ptrdiff_t)buf & 3)) { c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); len--; } - buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; while (len >= 32) { DOLIT32; len -= 32; @@ -306,17 +289,17 @@ local unsigned long crc32_big(crc, buf, len) const unsigned char FAR *buf; unsigned len; { - register u4 c; - register const u4 FAR *buf4; + register z_crc_t c; + register const z_crc_t FAR *buf4; - c = REV((u4)crc); + c = ZSWAP32((z_crc_t)crc); c = ~c; while (len && ((ptrdiff_t)buf & 3)) { c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); len--; } - buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; buf4--; while (len >= 32) { DOBIG32; @@ -333,7 +316,7 @@ local unsigned long crc32_big(crc, buf, len) c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); } while (--len); c = ~c; - return (unsigned long)(REV(c)); + return (unsigned long)(ZSWAP32(c)); } #endif /* BYFOUR */ diff --git a/etc/c/zlib/crc32.h b/etc/c/zlib/crc32.h index 8053b6117c0..9e0c7781025 100644 --- a/etc/c/zlib/crc32.h +++ b/etc/c/zlib/crc32.h @@ -2,7 +2,7 @@ * Generated automatically by crc32.c */ -local const unsigned long FAR crc_table[TBLS][256] = +local const z_crc_t FAR crc_table[TBLS][256] = { { 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, diff --git a/etc/c/zlib/deflate.c b/etc/c/zlib/deflate.c index 5c4022f3d47..696957705b7 100644 --- a/etc/c/zlib/deflate.c +++ b/etc/c/zlib/deflate.c @@ -1,5 +1,5 @@ /* deflate.c -- compress data using the deflation algorithm - * Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + * Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -37,7 +37,7 @@ * REFERENCES * * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". - * Available in http://www.ietf.org/rfc/rfc1951.txt + * Available in http://tools.ietf.org/html/rfc1951 * * A description of the Rabin and Karp algorithm is given in the book * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. @@ -52,7 +52,7 @@ #include "deflate.h" const char deflate_copyright[] = - " deflate 1.2.5 Copyright 1995-2010 Jean-loup Gailly and Mark Adler "; + " deflate 1.2.8 Copyright 1995-2013 Jean-loup Gailly and Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -155,6 +155,9 @@ local const config configuration_table[10] = { struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ #endif +/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */ +#define RANK(f) (((f) << 1) - ((f) > 4 ? 9 : 0)) + /* =========================================================================== * Update a hash value with the given input byte * IN assertion: all calls to to UPDATE_HASH are made with consecutive @@ -235,10 +238,19 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, strm->msg = Z_NULL; if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else strm->zalloc = zcalloc; strm->opaque = (voidpf)0; +#endif } - if (strm->zfree == (free_func)0) strm->zfree = zcfree; + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif #ifdef FASTEST if (level != 0) level = 1; @@ -293,7 +305,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || s->pending_buf == Z_NULL) { s->status = FINISH_STATE; - strm->msg = (char*)ERR_MSG(Z_MEM_ERROR); + strm->msg = ERR_MSG(Z_MEM_ERROR); deflateEnd (strm); return Z_MEM_ERROR; } @@ -314,43 +326,70 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) uInt dictLength; { deflate_state *s; - uInt length = dictLength; - uInt n; - IPos hash_head = 0; + uInt str, n; + int wrap; + unsigned avail; + z_const unsigned char *next; - if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL || - strm->state->wrap == 2 || - (strm->state->wrap == 1 && strm->state->status != INIT_STATE)) + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL) return Z_STREAM_ERROR; - s = strm->state; - if (s->wrap) - strm->adler = adler32(strm->adler, dictionary, dictLength); + wrap = s->wrap; + if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead) + return Z_STREAM_ERROR; - if (length < MIN_MATCH) return Z_OK; - if (length > s->w_size) { - length = s->w_size; - dictionary += dictLength - length; /* use the tail of the dictionary */ + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap == 1) + strm->adler = adler32(strm->adler, dictionary, dictLength); + s->wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s->w_size) { + if (wrap == 0) { /* already empty otherwise */ + CLEAR_HASH(s); + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } + dictionary += dictLength - s->w_size; /* use the tail */ + dictLength = s->w_size; } - zmemcpy(s->window, dictionary, length); - s->strstart = length; - s->block_start = (long)length; - /* Insert all strings in the hash table (except for the last two bytes). - * s->lookahead stays null, so s->ins_h will be recomputed at the next - * call of fill_window. - */ - s->ins_h = s->window[0]; - UPDATE_HASH(s, s->ins_h, s->window[1]); - for (n = 0; n <= length - MIN_MATCH; n++) { - INSERT_STRING(s, n, hash_head); + /* insert dictionary into window and hash */ + avail = strm->avail_in; + next = strm->next_in; + strm->avail_in = dictLength; + strm->next_in = (z_const Bytef *)dictionary; + fill_window(s); + while (s->lookahead >= MIN_MATCH) { + str = s->strstart; + n = s->lookahead - (MIN_MATCH-1); + do { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + } while (--n); + s->strstart = str; + s->lookahead = MIN_MATCH-1; + fill_window(s); } - if (hash_head) hash_head = 0; /* to make compiler happy */ + s->strstart += s->lookahead; + s->block_start = (long)s->strstart; + s->insert = s->lookahead; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + strm->next_in = next; + strm->avail_in = avail; + s->wrap = wrap; return Z_OK; } /* ========================================================================= */ -int ZEXPORT deflateReset (strm) +int ZEXPORT deflateResetKeep (strm) z_streamp strm; { deflate_state *s; @@ -380,11 +419,22 @@ int ZEXPORT deflateReset (strm) s->last_flush = Z_NO_FLUSH; _tr_init(s); - lm_init(s); return Z_OK; } +/* ========================================================================= */ +int ZEXPORT deflateReset (strm) + z_streamp strm; +{ + int ret; + + ret = deflateResetKeep(strm); + if (ret == Z_OK) + lm_init(strm->state); + return ret; +} + /* ========================================================================= */ int ZEXPORT deflateSetHeader (strm, head) z_streamp strm; @@ -396,15 +446,43 @@ int ZEXPORT deflateSetHeader (strm, head) return Z_OK; } +/* ========================================================================= */ +int ZEXPORT deflatePending (strm, pending, bits) + unsigned *pending; + int *bits; + z_streamp strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (pending != Z_NULL) + *pending = strm->state->pending; + if (bits != Z_NULL) + *bits = strm->state->bi_valid; + return Z_OK; +} + /* ========================================================================= */ int ZEXPORT deflatePrime (strm, bits, value) z_streamp strm; int bits; int value; { + deflate_state *s; + int put; + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; - strm->state->bi_valid = bits; - strm->state->bi_buf = (ush)(value & ((1 << bits) - 1)); + s = strm->state; + if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; + do { + put = Buf_size - s->bi_valid; + if (put > bits) + put = bits; + s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid); + s->bi_valid += put; + _tr_flush_bits(s); + value >>= put; + bits -= put; + } while (bits); return Z_OK; } @@ -435,6 +513,8 @@ int ZEXPORT deflateParams(strm, level, strategy) strm->total_in != 0) { /* Flush the last buffer: */ err = deflate(strm, Z_BLOCK); + if (err == Z_BUF_ERROR && s->pending == 0) + err = Z_OK; } if (s->level != level) { s->level = level; @@ -562,19 +642,22 @@ local void putShortMSB (s, b) local void flush_pending(strm) z_streamp strm; { - unsigned len = strm->state->pending; + unsigned len; + deflate_state *s = strm->state; + _tr_flush_bits(s); + len = s->pending; if (len > strm->avail_out) len = strm->avail_out; if (len == 0) return; - zmemcpy(strm->next_out, strm->state->pending_out, len); + zmemcpy(strm->next_out, s->pending_out, len); strm->next_out += len; - strm->state->pending_out += len; + s->pending_out += len; strm->total_out += len; strm->avail_out -= len; - strm->state->pending -= len; - if (strm->state->pending == 0) { - strm->state->pending_out = strm->state->pending_buf; + s->pending -= len; + if (s->pending == 0) { + s->pending_out = s->pending_buf; } } @@ -801,7 +884,7 @@ int ZEXPORT deflate (strm, flush) * flushes. For repeated and useless calls with Z_FINISH, we keep * returning Z_STREAM_END instead of Z_BUF_ERROR. */ - } else if (strm->avail_in == 0 && flush <= old_flush && + } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) && flush != Z_FINISH) { ERR_RETURN(strm, Z_BUF_ERROR); } @@ -850,6 +933,7 @@ int ZEXPORT deflate (strm, flush) if (s->lookahead == 0) { s->strstart = 0; s->block_start = 0L; + s->insert = 0; } } } @@ -945,12 +1029,12 @@ int ZEXPORT deflateCopy (dest, source) ss = source->state; - zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); if (ds == Z_NULL) return Z_MEM_ERROR; dest->state = (struct internal_state FAR *) ds; - zmemcpy(ds, ss, sizeof(deflate_state)); + zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state)); ds->strm = dest; ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); @@ -966,8 +1050,8 @@ int ZEXPORT deflateCopy (dest, source) } /* following zmemcpy do not work for 16-bit MSDOS */ zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); - zmemcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos)); - zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); @@ -1001,15 +1085,15 @@ local int read_buf(strm, buf, size) strm->avail_in -= len; + zmemcpy(buf, strm->next_in, len); if (strm->state->wrap == 1) { - strm->adler = adler32(strm->adler, strm->next_in, len); + strm->adler = adler32(strm->adler, buf, len); } #ifdef GZIP else if (strm->state->wrap == 2) { - strm->adler = crc32(strm->adler, strm->next_in, len); + strm->adler = crc32(strm->adler, buf, len); } #endif - zmemcpy(buf, strm->next_in, len); strm->next_in += len; strm->total_in += len; @@ -1036,6 +1120,7 @@ local void lm_init (s) s->strstart = 0; s->block_start = 0L; s->lookahead = 0; + s->insert = 0; s->match_length = s->prev_length = MIN_MATCH-1; s->match_available = 0; s->ins_h = 0; @@ -1310,6 +1395,8 @@ local void fill_window(s) unsigned more; /* Amount of free space at the end of the window. */ uInt wsize = s->w_size; + Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + do { more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); @@ -1362,7 +1449,7 @@ local void fill_window(s) #endif more += wsize; } - if (s->strm->avail_in == 0) return; + if (s->strm->avail_in == 0) break; /* If there was no sliding: * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && @@ -1381,12 +1468,24 @@ local void fill_window(s) s->lookahead += n; /* Initialize the hash value now that we have some input: */ - if (s->lookahead >= MIN_MATCH) { - s->ins_h = s->window[s->strstart]; - UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); + if (s->lookahead + s->insert >= MIN_MATCH) { + uInt str = s->strstart - s->insert; + s->ins_h = s->window[str]; + UPDATE_HASH(s, s->ins_h, s->window[str + 1]); #if MIN_MATCH != 3 Call UPDATE_HASH() MIN_MATCH-3 more times #endif + while (s->insert) { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + s->insert--; + if (s->lookahead + s->insert < MIN_MATCH) + break; + } } /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, * but this is not important since only literal bytes will be emitted. @@ -1427,6 +1526,9 @@ local void fill_window(s) s->high_water += init; } } + + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "not enough room for search"); } /* =========================================================================== @@ -1506,8 +1608,14 @@ local block_state deflate_stored(s, flush) FLUSH_BLOCK(s, 0); } } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if ((long)s->strstart > s->block_start) + FLUSH_BLOCK(s, 0); + return block_done; } /* =========================================================================== @@ -1603,8 +1711,14 @@ local block_state deflate_fast(s, flush) } if (bflush) FLUSH_BLOCK(s, 0); } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } #ifndef FASTEST @@ -1728,8 +1842,14 @@ local block_state deflate_slow(s, flush) _tr_tally_lit(s, s->window[s->strstart-1], bflush); s->match_available = 0; } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } #endif /* FASTEST */ @@ -1749,11 +1869,11 @@ local block_state deflate_rle(s, flush) for (;;) { /* Make sure that we always have enough lookahead, except * at the end of the input file. We need MAX_MATCH bytes - * for the longest encodable run. + * for the longest run, plus one for the unrolled loop. */ - if (s->lookahead < MAX_MATCH) { + if (s->lookahead <= MAX_MATCH) { fill_window(s); - if (s->lookahead < MAX_MATCH && flush == Z_NO_FLUSH) { + if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) { return need_more; } if (s->lookahead == 0) break; /* flush the current block */ @@ -1776,6 +1896,7 @@ local block_state deflate_rle(s, flush) if (s->match_length > s->lookahead) s->match_length = s->lookahead; } + Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); } /* Emit match if have run of MIN_MATCH or longer, else emit literal */ @@ -1796,8 +1917,14 @@ local block_state deflate_rle(s, flush) } if (bflush) FLUSH_BLOCK(s, 0); } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } /* =========================================================================== @@ -1829,6 +1956,12 @@ local block_state deflate_huff(s, flush) s->strstart++; if (bflush) FLUSH_BLOCK(s, 0); } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } diff --git a/etc/c/zlib/deflate.h b/etc/c/zlib/deflate.h index cbf0d1ea5d9..ce0299edd19 100644 --- a/etc/c/zlib/deflate.h +++ b/etc/c/zlib/deflate.h @@ -1,5 +1,5 @@ /* deflate.h -- internal compression state - * Copyright (C) 1995-2010 Jean-loup Gailly + * Copyright (C) 1995-2012 Jean-loup Gailly * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -48,6 +48,9 @@ #define MAX_BITS 15 /* All codes must not exceed MAX_BITS bits */ +#define Buf_size 16 +/* size of bit buffer in bi_buf */ + #define INIT_STATE 42 #define EXTRA_STATE 69 #define NAME_STATE 73 @@ -101,7 +104,7 @@ typedef struct internal_state { int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ gz_headerp gzhead; /* gzip header information to write */ uInt gzindex; /* where in extra, name, or comment */ - Byte method; /* STORED (for zip only) or DEFLATED */ + Byte method; /* can only be DEFLATED */ int last_flush; /* value of flush param for previous deflate call */ /* used by deflate.c: */ @@ -188,7 +191,7 @@ typedef struct internal_state { int nice_match; /* Stop searching when current match exceeds this */ /* used by trees.c: */ - /* Didn't use ct_data typedef below to supress compiler warning */ + /* Didn't use ct_data typedef below to suppress compiler warning */ struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ @@ -244,7 +247,7 @@ typedef struct internal_state { ulg opt_len; /* bit length of current block with optimal trees */ ulg static_len; /* bit length of current block with static trees */ uInt matches; /* number of string matches in current block */ - int last_eob_len; /* bit length of EOB code for last block */ + uInt insert; /* bytes at end of window left to insert */ #ifdef DEBUG ulg compressed_len; /* total bit length of compressed file mod 2^32 */ @@ -294,6 +297,7 @@ void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, int last)); +void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, int last)); diff --git a/etc/c/zlib/example.c b/etc/c/zlib/example.c index 604736f15f6..138a699bd53 100644 --- a/etc/c/zlib/example.c +++ b/etc/c/zlib/example.c @@ -1,5 +1,5 @@ /* example.c -- usage example of the zlib compression library - * Copyright (C) 1995-2006 Jean-loup Gailly. + * Copyright (C) 1995-2006, 2011 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -26,7 +26,7 @@ } \ } -const char hello[] = "hello, hello!"; +z_const char hello[] = "hello, hello!"; /* "hello world" would be more standard, but the repeated "hello" * stresses the compression code better, sorry... */ @@ -34,10 +34,6 @@ const char hello[] = "hello, hello!"; const char dictionary[] = "hello"; uLong dictId; /* Adler32 value of the dictionary */ -void test_compress OF((Byte *compr, uLong comprLen, - Byte *uncompr, uLong uncomprLen)); -void test_gzio OF((const char *fname, - Byte *uncompr, uLong uncomprLen)); void test_deflate OF((Byte *compr, uLong comprLen)); void test_inflate OF((Byte *compr, uLong comprLen, Byte *uncompr, uLong uncomprLen)); @@ -53,6 +49,39 @@ void test_dict_inflate OF((Byte *compr, uLong comprLen, Byte *uncompr, uLong uncomprLen)); int main OF((int argc, char *argv[])); + +#ifdef Z_SOLO + +void *myalloc OF((void *, unsigned, unsigned)); +void myfree OF((void *, void *)); + +void *myalloc(q, n, m) + void *q; + unsigned n, m; +{ + q = Z_NULL; + return calloc(n, m); +} + +void myfree(void *q, void *p) +{ + q = Z_NULL; + free(p); +} + +static alloc_func zalloc = myalloc; +static free_func zfree = myfree; + +#else /* !Z_SOLO */ + +static alloc_func zalloc = (alloc_func)0; +static free_func zfree = (free_func)0; + +void test_compress OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_gzio OF((const char *fname, + Byte *uncompr, uLong uncomprLen)); + /* =========================================================================== * Test compress() and uncompress() */ @@ -163,6 +192,8 @@ void test_gzio(fname, uncompr, uncomprLen) #endif } +#endif /* Z_SOLO */ + /* =========================================================================== * Test deflate() with small buffers */ @@ -174,14 +205,14 @@ void test_deflate(compr, comprLen) int err; uLong len = (uLong)strlen(hello)+1; - c_stream.zalloc = (alloc_func)0; - c_stream.zfree = (free_func)0; + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; c_stream.opaque = (voidpf)0; err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); CHECK_ERR(err, "deflateInit"); - c_stream.next_in = (Bytef*)hello; + c_stream.next_in = (z_const unsigned char *)hello; c_stream.next_out = compr; while (c_stream.total_in != len && c_stream.total_out < comprLen) { @@ -213,8 +244,8 @@ void test_inflate(compr, comprLen, uncompr, uncomprLen) strcpy((char*)uncompr, "garbage"); - d_stream.zalloc = (alloc_func)0; - d_stream.zfree = (free_func)0; + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; d_stream.opaque = (voidpf)0; d_stream.next_in = compr; @@ -252,8 +283,8 @@ void test_large_deflate(compr, comprLen, uncompr, uncomprLen) z_stream c_stream; /* compression stream */ int err; - c_stream.zalloc = (alloc_func)0; - c_stream.zfree = (free_func)0; + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; c_stream.opaque = (voidpf)0; err = deflateInit(&c_stream, Z_BEST_SPEED); @@ -309,8 +340,8 @@ void test_large_inflate(compr, comprLen, uncompr, uncomprLen) strcpy((char*)uncompr, "garbage"); - d_stream.zalloc = (alloc_func)0; - d_stream.zfree = (free_func)0; + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; d_stream.opaque = (voidpf)0; d_stream.next_in = compr; @@ -349,14 +380,14 @@ void test_flush(compr, comprLen) int err; uInt len = (uInt)strlen(hello)+1; - c_stream.zalloc = (alloc_func)0; - c_stream.zfree = (free_func)0; + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; c_stream.opaque = (voidpf)0; err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); CHECK_ERR(err, "deflateInit"); - c_stream.next_in = (Bytef*)hello; + c_stream.next_in = (z_const unsigned char *)hello; c_stream.next_out = compr; c_stream.avail_in = 3; c_stream.avail_out = (uInt)*comprLen; @@ -388,8 +419,8 @@ void test_sync(compr, comprLen, uncompr, uncomprLen) strcpy((char*)uncompr, "garbage"); - d_stream.zalloc = (alloc_func)0; - d_stream.zfree = (free_func)0; + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; d_stream.opaque = (voidpf)0; d_stream.next_in = compr; @@ -430,22 +461,22 @@ void test_dict_deflate(compr, comprLen) z_stream c_stream; /* compression stream */ int err; - c_stream.zalloc = (alloc_func)0; - c_stream.zfree = (free_func)0; + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; c_stream.opaque = (voidpf)0; err = deflateInit(&c_stream, Z_BEST_COMPRESSION); CHECK_ERR(err, "deflateInit"); err = deflateSetDictionary(&c_stream, - (const Bytef*)dictionary, sizeof(dictionary)); + (const Bytef*)dictionary, (int)sizeof(dictionary)); CHECK_ERR(err, "deflateSetDictionary"); dictId = c_stream.adler; c_stream.next_out = compr; c_stream.avail_out = (uInt)comprLen; - c_stream.next_in = (Bytef*)hello; + c_stream.next_in = (z_const unsigned char *)hello; c_stream.avail_in = (uInt)strlen(hello)+1; err = deflate(&c_stream, Z_FINISH); @@ -469,8 +500,8 @@ void test_dict_inflate(compr, comprLen, uncompr, uncomprLen) strcpy((char*)uncompr, "garbage"); - d_stream.zalloc = (alloc_func)0; - d_stream.zfree = (free_func)0; + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; d_stream.opaque = (voidpf)0; d_stream.next_in = compr; @@ -491,7 +522,7 @@ void test_dict_inflate(compr, comprLen, uncompr, uncomprLen) exit(1); } err = inflateSetDictionary(&d_stream, (const Bytef*)dictionary, - sizeof(dictionary)); + (int)sizeof(dictionary)); } CHECK_ERR(err, "inflate with dict"); } @@ -540,10 +571,15 @@ int main(argc, argv) printf("out of memory\n"); exit(1); } + +#ifdef Z_SOLO + argc = strlen(argv[0]); +#else test_compress(compr, comprLen, uncompr, uncomprLen); test_gzio((argc > 1 ? argv[1] : TESTFILE), uncompr, uncomprLen); +#endif test_deflate(compr, comprLen); test_inflate(compr, comprLen, uncompr, uncomprLen); diff --git a/etc/c/zlib/gzguts.h b/etc/c/zlib/gzguts.h index 0f8fb79f87d..d87659d0319 100644 --- a/etc/c/zlib/gzguts.h +++ b/etc/c/zlib/gzguts.h @@ -1,5 +1,5 @@ /* gzguts.h -- zlib internal header definitions for gz* operations - * Copyright (C) 2004, 2005, 2010 Mark Adler + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -12,7 +12,7 @@ # endif #endif -#if ((__GNUC__-0) * 10 + __GNUC_MINOR__-0 >= 33) && !defined(NO_VIZ) +#ifdef HAVE_HIDDEN # define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) #else # define ZLIB_INTERNAL @@ -27,13 +27,80 @@ #endif #include +#ifdef _WIN32 +# include +#endif + +#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32) +# include +#endif + +#ifdef WINAPI_FAMILY +# define open _open +# define read _read +# define write _write +# define close _close +#endif + #ifdef NO_DEFLATE /* for compatibility with old definition */ # define NO_GZCOMPRESS #endif +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS +/* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 +/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) +# define vsnprintf _vsnprintf +# endif +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +# ifdef VMS +# define NO_vsnprintf +# endif +# ifdef __OS400__ +# define NO_vsnprintf +# endif +# ifdef __MVS__ +# define NO_vsnprintf +# endif +#endif + +/* unlike snprintf (which is required in C99, yet still not supported by + Microsoft more than a decade later!), _snprintf does not guarantee null + termination of the result -- however this is only used in gzlib.c where + the result is assured to fit in the space provided */ #ifdef _MSC_VER -# include -# define vsnprintf _vsnprintf +# define snprintf _snprintf #endif #ifndef local @@ -52,7 +119,7 @@ # include # define zstrerror() gz_strwinerror((DWORD)GetLastError()) #else -# ifdef STDC +# ifndef NO_STRERROR # include # define zstrerror() strerror(errno) # else @@ -68,7 +135,15 @@ ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); #endif -/* default i/o buffer size -- double this for output when reading */ +/* default memLevel */ +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif + +/* default i/o buffer size -- double this for output when reading (this and + twice this must be able to fit in an unsigned type) */ #define GZBUFSIZE 8192 /* gzip modes, also provide a little integrity check on the passed structure */ @@ -84,23 +159,25 @@ /* internal gzip file state data structure */ typedef struct { + /* exposed contents for gzgetc() macro */ + struct gzFile_s x; /* "x" for exposed */ + /* x.have: number of bytes available at x.next */ + /* x.next: next output data to deliver or write */ + /* x.pos: current position in uncompressed data */ /* used for both reading and writing */ int mode; /* see gzip modes above */ int fd; /* file descriptor */ char *path; /* path or fd for error messages */ - z_off64_t pos; /* current position in uncompressed data */ unsigned size; /* buffer size, zero if not allocated yet */ unsigned want; /* requested buffer size, default is GZBUFSIZE */ unsigned char *in; /* input buffer */ unsigned char *out; /* output buffer (double-sized when reading) */ - unsigned char *next; /* next output data to deliver or write */ + int direct; /* 0 if processing gzip, 1 if transparent */ /* just for reading */ - unsigned have; /* amount of output data unused at next */ - int eof; /* true if end of input file reached */ - z_off64_t start; /* where the gzip data started, for rewinding */ - z_off64_t raw; /* where the raw data started, for seeking */ int how; /* 0: get header, 1: copy, 2: decompress */ - int direct; /* true if last read direct, false if gzip */ + z_off64_t start; /* where the gzip data started, for rewinding */ + int eof; /* true if end of input file reached */ + int past; /* true if read requested past end */ /* just for writing */ int level; /* compression level */ int strategy; /* compression strategy */ diff --git a/etc/c/zlib/gzlib.c b/etc/c/zlib/gzlib.c index 603e60ed544..fae202ef890 100644 --- a/etc/c/zlib/gzlib.c +++ b/etc/c/zlib/gzlib.c @@ -1,19 +1,23 @@ /* gzlib.c -- zlib functions common to reading and writing gzip files - * Copyright (C) 2004, 2010 Mark Adler + * Copyright (C) 2004, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ #include "gzguts.h" +#if defined(_WIN32) && !defined(__BORLANDC__) +# define LSEEK _lseeki64 +#else #if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 # define LSEEK lseek64 #else # define LSEEK lseek #endif +#endif /* Local functions */ local void gz_reset OF((gz_statep)); -local gzFile gz_open OF((const char *, int, const char *)); +local gzFile gz_open OF((const void *, int, const char *)); #if defined UNDER_CE @@ -71,28 +75,40 @@ char ZLIB_INTERNAL *gz_strwinerror (error) local void gz_reset(state) gz_statep state; { + state->x.have = 0; /* no output data available */ if (state->mode == GZ_READ) { /* for reading ... */ - state->have = 0; /* no output data available */ state->eof = 0; /* not at end of file */ + state->past = 0; /* have not read past end yet */ state->how = LOOK; /* look for gzip header */ - state->direct = 1; /* default for empty file */ } state->seek = 0; /* no seek request pending */ gz_error(state, Z_OK, NULL); /* clear error */ - state->pos = 0; /* no uncompressed data yet */ + state->x.pos = 0; /* no uncompressed data yet */ state->strm.avail_in = 0; /* no input data yet */ } /* Open a gzip file either by name or file descriptor. */ local gzFile gz_open(path, fd, mode) - const char *path; + const void *path; int fd; const char *mode; { gz_statep state; + size_t len; + int oflag; +#ifdef O_CLOEXEC + int cloexec = 0; +#endif +#ifdef O_EXCL + int exclusive = 0; +#endif + + /* check input */ + if (path == NULL) + return NULL; /* allocate gzFile structure to return */ - state = malloc(sizeof(gz_state)); + state = (gz_statep)malloc(sizeof(gz_state)); if (state == NULL) return NULL; state->size = 0; /* no buffers allocated yet */ @@ -103,6 +119,7 @@ local gzFile gz_open(path, fd, mode) state->mode = GZ_NONE; state->level = Z_DEFAULT_COMPRESSION; state->strategy = Z_DEFAULT_STRATEGY; + state->direct = 0; while (*mode) { if (*mode >= '0' && *mode <= '9') state->level = *mode - '0'; @@ -124,6 +141,16 @@ local gzFile gz_open(path, fd, mode) return NULL; case 'b': /* ignore -- will request binary anyway */ break; +#ifdef O_CLOEXEC + case 'e': + cloexec = 1; + break; +#endif +#ifdef O_EXCL + case 'x': + exclusive = 1; + break; +#endif case 'f': state->strategy = Z_FILTERED; break; @@ -135,6 +162,10 @@ local gzFile gz_open(path, fd, mode) break; case 'F': state->strategy = Z_FIXED; + break; + case 'T': + state->direct = 1; + break; default: /* could consider as an error, but just ignore */ ; } @@ -147,30 +178,71 @@ local gzFile gz_open(path, fd, mode) return NULL; } + /* can't force transparent read */ + if (state->mode == GZ_READ) { + if (state->direct) { + free(state); + return NULL; + } + state->direct = 1; /* for empty file */ + } + /* save the path name for error messages */ - state->path = malloc(strlen(path) + 1); +#ifdef _WIN32 + if (fd == -2) { + len = wcstombs(NULL, path, 0); + if (len == (size_t)-1) + len = 0; + } + else +#endif + len = strlen((const char *)path); + state->path = (char *)malloc(len + 1); if (state->path == NULL) { free(state); return NULL; } - strcpy(state->path, path); +#ifdef _WIN32 + if (fd == -2) + if (len) + wcstombs(state->path, path, len + 1); + else + *(state->path) = 0; + else +#endif +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(state->path, len + 1, "%s", (const char *)path); +#else + strcpy(state->path, path); +#endif - /* open the file with the appropriate mode (or just use fd) */ - state->fd = fd != -1 ? fd : - open(path, + /* compute the flags for open() */ + oflag = #ifdef O_LARGEFILE - O_LARGEFILE | + O_LARGEFILE | #endif #ifdef O_BINARY - O_BINARY | + O_BINARY | +#endif +#ifdef O_CLOEXEC + (cloexec ? O_CLOEXEC : 0) | +#endif + (state->mode == GZ_READ ? + O_RDONLY : + (O_WRONLY | O_CREAT | +#ifdef O_EXCL + (exclusive ? O_EXCL : 0) | +#endif + (state->mode == GZ_WRITE ? + O_TRUNC : + O_APPEND))); + + /* open the file with the appropriate flags (or just use fd) */ + state->fd = fd > -1 ? fd : ( +#ifdef _WIN32 + fd == -2 ? _wopen(path, oflag, 0666) : #endif - (state->mode == GZ_READ ? - O_RDONLY : - (O_WRONLY | O_CREAT | ( - state->mode == GZ_WRITE ? - O_TRUNC : - O_APPEND))), - 0666); + open((const char *)path, oflag, 0666)); if (state->fd == -1) { free(state->path); free(state); @@ -216,14 +288,28 @@ gzFile ZEXPORT gzdopen(fd, mode) char *path; /* identifier for error messages */ gzFile gz; - if (fd == -1 || (path = malloc(7 + 3 * sizeof(int))) == NULL) + if (fd == -1 || (path = (char *)malloc(7 + 3 * sizeof(int))) == NULL) return NULL; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(path, 7 + 3 * sizeof(int), "", fd); /* for debugging */ +#else sprintf(path, "", fd); /* for debugging */ +#endif gz = gz_open(path, fd, mode); free(path); return gz; } +/* -- see zlib.h -- */ +#ifdef _WIN32 +gzFile ZEXPORT gzopen_w(path, mode) + const wchar_t *path; + const char *mode; +{ + return gz_open(path, -2, mode); +} +#endif + /* -- see zlib.h -- */ int ZEXPORT gzbuffer(file, size) gzFile file; @@ -243,8 +329,8 @@ int ZEXPORT gzbuffer(file, size) return -1; /* check and set requested size */ - if (size == 0) - return -1; + if (size < 2) + size = 2; /* need two bytes to check magic header */ state->want = size; return 0; } @@ -261,7 +347,8 @@ int ZEXPORT gzrewind(file) state = (gz_statep)file; /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* back up and start over */ @@ -289,7 +376,7 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) return -1; /* check that there's no error */ - if (state->err != Z_OK) + if (state->err != Z_OK && state->err != Z_BUF_ERROR) return -1; /* can only seek from start or relative to current position */ @@ -298,31 +385,32 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) /* normalize offset to a SEEK_CUR specification */ if (whence == SEEK_SET) - offset -= state->pos; + offset -= state->x.pos; else if (state->seek) offset += state->skip; state->seek = 0; /* if within raw area while reading, just go there */ if (state->mode == GZ_READ && state->how == COPY && - state->pos + offset >= state->raw) { - ret = LSEEK(state->fd, offset - state->have, SEEK_CUR); + state->x.pos + offset >= 0) { + ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR); if (ret == -1) return -1; - state->have = 0; + state->x.have = 0; state->eof = 0; + state->past = 0; state->seek = 0; gz_error(state, Z_OK, NULL); state->strm.avail_in = 0; - state->pos += offset; - return state->pos; + state->x.pos += offset; + return state->x.pos; } /* calculate skip amount, rewinding if needed for back seek when reading */ if (offset < 0) { if (state->mode != GZ_READ) /* writing -- can't go backwards */ return -1; - offset += state->pos; + offset += state->x.pos; if (offset < 0) /* before start of file! */ return -1; if (gzrewind(file) == -1) /* rewind, then skip to offset */ @@ -331,11 +419,11 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) /* if reading, skip what's in output buffer (one less gzgetc() check) */ if (state->mode == GZ_READ) { - n = GT_OFF(state->have) || (z_off64_t)state->have > offset ? - (unsigned)offset : state->have; - state->have -= n; - state->next += n; - state->pos += n; + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ? + (unsigned)offset : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; offset -= n; } @@ -344,7 +432,7 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) state->seek = 1; state->skip = offset; } - return state->pos + offset; + return state->x.pos + offset; } /* -- see zlib.h -- */ @@ -373,7 +461,7 @@ z_off64_t ZEXPORT gztell64(file) return -1; /* return position */ - return state->pos + (state->seek ? state->skip : 0); + return state->x.pos + (state->seek ? state->skip : 0); } /* -- see zlib.h -- */ @@ -433,8 +521,7 @@ int ZEXPORT gzeof(file) return 0; /* return end-of-file state */ - return state->mode == GZ_READ ? - (state->eof && state->strm.avail_in == 0 && state->have == 0) : 0; + return state->mode == GZ_READ ? state->past : 0; } /* -- see zlib.h -- */ @@ -454,7 +541,8 @@ const char * ZEXPORT gzerror(file, errnum) /* return error information */ if (errnum != NULL) *errnum = state->err; - return state->msg == NULL ? "" : state->msg; + return state->err == Z_MEM_ERROR ? "out of memory" : + (state->msg == NULL ? "" : state->msg); } /* -- see zlib.h -- */ @@ -471,8 +559,10 @@ void ZEXPORT gzclearerr(file) return; /* clear error and end-of-file */ - if (state->mode == GZ_READ) + if (state->mode == GZ_READ) { state->eof = 0; + state->past = 0; + } gz_error(state, Z_OK, NULL); } @@ -494,26 +584,33 @@ void ZLIB_INTERNAL gz_error(state, err, msg) state->msg = NULL; } + /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */ + if (err != Z_OK && err != Z_BUF_ERROR) + state->x.have = 0; + /* set error code, and if no message, then done */ state->err = err; if (msg == NULL) return; - /* for an out of memory error, save as static string */ - if (err == Z_MEM_ERROR) { - state->msg = (char *)msg; + /* for an out of memory error, return literal string when requested */ + if (err == Z_MEM_ERROR) return; - } /* construct error message with path */ - if ((state->msg = malloc(strlen(state->path) + strlen(msg) + 3)) == NULL) { + if ((state->msg = (char *)malloc(strlen(state->path) + strlen(msg) + 3)) == + NULL) { state->err = Z_MEM_ERROR; - state->msg = (char *)"out of memory"; return; } +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(state->msg, strlen(state->path) + strlen(msg) + 3, + "%s%s%s", state->path, ": ", msg); +#else strcpy(state->msg, state->path); strcat(state->msg, ": "); strcat(state->msg, msg); +#endif return; } diff --git a/etc/c/zlib/gzread.c b/etc/c/zlib/gzread.c index 548201ab009..bf4538eb274 100644 --- a/etc/c/zlib/gzread.c +++ b/etc/c/zlib/gzread.c @@ -1,5 +1,5 @@ /* gzread.c -- zlib functions for reading gzip files - * Copyright (C) 2004, 2005, 2010 Mark Adler + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -8,10 +8,9 @@ /* Local functions */ local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *)); local int gz_avail OF((gz_statep)); -local int gz_next4 OF((gz_statep, unsigned long *)); -local int gz_head OF((gz_statep)); +local int gz_look OF((gz_statep)); local int gz_decomp OF((gz_statep)); -local int gz_make OF((gz_statep)); +local int gz_fetch OF((gz_statep)); local int gz_skip OF((gz_statep, z_off64_t)); /* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from @@ -46,73 +45,54 @@ local int gz_load(state, buf, len, have) error, 0 otherwise. Note that the eof flag is set when the end of the input file is reached, even though there may be unused data in the buffer. Once that data has been used, no more attempts will be made to read the file. - gz_avail() assumes that strm->avail_in == 0. */ + If strm->avail_in != 0, then the current data is moved to the beginning of + the input buffer, and then the remainder of the buffer is loaded with the + available data from the input file. */ local int gz_avail(state) gz_statep state; { + unsigned got; z_streamp strm = &(state->strm); - if (state->err != Z_OK) + if (state->err != Z_OK && state->err != Z_BUF_ERROR) return -1; if (state->eof == 0) { - if (gz_load(state, state->in, state->size, - (unsigned *)&(strm->avail_in)) == -1) + if (strm->avail_in) { /* copy what's there to the start */ + unsigned char *p = state->in; + unsigned const char *q = strm->next_in; + unsigned n = strm->avail_in; + do { + *p++ = *q++; + } while (--n); + } + if (gz_load(state, state->in + strm->avail_in, + state->size - strm->avail_in, &got) == -1) return -1; + strm->avail_in += got; strm->next_in = state->in; } return 0; } -/* Get next byte from input, or -1 if end or error. */ -#define NEXT() ((strm->avail_in == 0 && gz_avail(state) == -1) ? -1 : \ - (strm->avail_in == 0 ? -1 : \ - (strm->avail_in--, *(strm->next_in)++))) - -/* Get a four-byte little-endian integer and return 0 on success and the value - in *ret. Otherwise -1 is returned and *ret is not modified. */ -local int gz_next4(state, ret) - gz_statep state; - unsigned long *ret; -{ - int ch; - unsigned long val; - z_streamp strm = &(state->strm); - - val = NEXT(); - val += (unsigned)NEXT() << 8; - val += (unsigned long)NEXT() << 16; - ch = NEXT(); - if (ch == -1) - return -1; - val += (unsigned long)ch << 24; - *ret = val; - return 0; -} - -/* Look for gzip header, set up for inflate or copy. state->have must be zero. +/* Look for gzip header, set up for inflate or copy. state->x.have must be 0. If this is the first time in, allocate required memory. state->how will be left unchanged if there is no more input data available, will be set to COPY if there is no gzip header and direct copying will be performed, or it will - be set to GZIP for decompression, and the gzip header will be skipped so - that the next available input data is the raw deflate stream. If direct - copying, then leftover input data from the input buffer will be copied to - the output buffer. In that case, all further file reads will be directly to - either the output buffer or a user buffer. If decompressing, the inflate - state and the check value will be initialized. gz_head() will return 0 on - success or -1 on failure. Failures may include read errors or gzip header - errors. */ -local int gz_head(state) + be set to GZIP for decompression. If direct copying, then leftover input + data from the input buffer will be copied to the output buffer. In that + case, all further file reads will be directly to either the output buffer or + a user buffer. If decompressing, the inflate state will be initialized. + gz_look() will return 0 on success or -1 on failure. */ +local int gz_look(state) gz_statep state; { z_streamp strm = &(state->strm); - int flags; - unsigned len; /* allocate read buffers and inflate memory */ if (state->size == 0) { /* allocate buffers */ - state->in = malloc(state->want); - state->out = malloc(state->want << 1); + state->in = (unsigned char *)malloc(state->want); + state->out = (unsigned char *)malloc(state->want << 1); if (state->in == NULL || state->out == NULL) { if (state->out != NULL) free(state->out); @@ -129,7 +109,7 @@ local int gz_head(state) state->strm.opaque = Z_NULL; state->strm.avail_in = 0; state->strm.next_in = Z_NULL; - if (inflateInit2(&(state->strm), -15) != Z_OK) { /* raw inflate */ + if (inflateInit2(&(state->strm), 15 + 16) != Z_OK) { /* gunzip */ free(state->out); free(state->in); state->size = 0; @@ -138,83 +118,45 @@ local int gz_head(state) } } - /* get some data in the input buffer */ - if (strm->avail_in == 0) { + /* get at least the magic bytes in the input buffer */ + if (strm->avail_in < 2) { if (gz_avail(state) == -1) return -1; if (strm->avail_in == 0) return 0; } - /* look for the gzip magic header bytes 31 and 139 */ - if (strm->next_in[0] == 31) { - strm->avail_in--; - strm->next_in++; - if (strm->avail_in == 0 && gz_avail(state) == -1) - return -1; - if (strm->avail_in && strm->next_in[0] == 139) { - /* we have a gzip header, woo hoo! */ - strm->avail_in--; - strm->next_in++; - - /* skip rest of header */ - if (NEXT() != 8) { /* compression method */ - gz_error(state, Z_DATA_ERROR, "unknown compression method"); - return -1; - } - flags = NEXT(); - if (flags & 0xe0) { /* reserved flag bits */ - gz_error(state, Z_DATA_ERROR, "unknown header flags set"); - return -1; - } - NEXT(); /* modification time */ - NEXT(); - NEXT(); - NEXT(); - NEXT(); /* extra flags */ - NEXT(); /* operating system */ - if (flags & 4) { /* extra field */ - len = (unsigned)NEXT(); - len += (unsigned)NEXT() << 8; - while (len--) - if (NEXT() < 0) - break; - } - if (flags & 8) /* file name */ - while (NEXT() > 0) - ; - if (flags & 16) /* comment */ - while (NEXT() > 0) - ; - if (flags & 2) { /* header crc */ - NEXT(); - NEXT(); - } - /* an unexpected end of file is not checked for here -- it will be - noticed on the first request for uncompressed data */ - - /* set up for decompression */ - inflateReset(strm); - strm->adler = crc32(0L, Z_NULL, 0); - state->how = GZIP; - state->direct = 0; - return 0; - } - else { - /* not a gzip file -- save first byte (31) and fall to raw i/o */ - state->out[0] = 31; - state->have = 1; - } + /* look for gzip magic bytes -- if there, do gzip decoding (note: there is + a logical dilemma here when considering the case of a partially written + gzip file, to wit, if a single 31 byte is written, then we cannot tell + whether this is a single-byte file, or just a partially written gzip + file -- for here we assume that if a gzip file is being written, then + the header will be written in a single operation, so that reading a + single byte is sufficient indication that it is not a gzip file) */ + if (strm->avail_in > 1 && + strm->next_in[0] == 31 && strm->next_in[1] == 139) { + inflateReset(strm); + state->how = GZIP; + state->direct = 0; + return 0; + } + + /* no gzip header -- if we were decoding gzip before, then this is trailing + garbage. Ignore the trailing garbage and finish. */ + if (state->direct == 0) { + strm->avail_in = 0; + state->eof = 1; + state->x.have = 0; + return 0; } - /* doing raw i/o, save start of raw data for seeking, copy any leftover - input to output -- this assumes that the output buffer is larger than - the input buffer, which also assures space for gzungetc() */ - state->raw = state->pos; - state->next = state->out; + /* doing raw i/o, copy any leftover input to output -- this assumes that + the output buffer is larger than the input buffer, which also assures + space for gzungetc() */ + state->x.next = state->out; if (strm->avail_in) { - memcpy(state->next + state->have, strm->next_in, strm->avail_in); - state->have += strm->avail_in; + memcpy(state->x.next, strm->next_in, strm->avail_in); + state->x.have = strm->avail_in; strm->avail_in = 0; } state->how = COPY; @@ -223,19 +165,15 @@ local int gz_head(state) } /* Decompress from input to the provided next_out and avail_out in the state. - If the end of the compressed data is reached, then verify the gzip trailer - check value and length (modulo 2^32). state->have and state->next are set - to point to the just decompressed data, and the crc is updated. If the - trailer is verified, state->how is reset to LOOK to look for the next gzip - stream or raw data, once state->have is depleted. Returns 0 on success, -1 - on failure. Failures may include invalid compressed data or a failed gzip - trailer verification. */ + On return, state->x.have and state->x.next point to the just decompressed + data. If the gzip stream completes, state->how is reset to LOOK to look for + the next gzip stream or raw data, once state->x.have is depleted. Returns 0 + on success, -1 on failure. */ local int gz_decomp(state) gz_statep state; { - int ret; + int ret = Z_OK; unsigned had; - unsigned long crc, len; z_streamp strm = &(state->strm); /* fill output buffer up to end of deflate stream */ @@ -245,15 +183,15 @@ local int gz_decomp(state) if (strm->avail_in == 0 && gz_avail(state) == -1) return -1; if (strm->avail_in == 0) { - gz_error(state, Z_DATA_ERROR, "unexpected end of file"); - return -1; + gz_error(state, Z_BUF_ERROR, "unexpected end of file"); + break; } /* decompress and handle errors */ ret = inflate(strm, Z_NO_FLUSH); if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) { gz_error(state, Z_STREAM_ERROR, - "internal error: inflate stream corrupt"); + "internal error: inflate stream corrupt"); return -1; } if (ret == Z_MEM_ERROR) { @@ -262,67 +200,55 @@ local int gz_decomp(state) } if (ret == Z_DATA_ERROR) { /* deflate stream invalid */ gz_error(state, Z_DATA_ERROR, - strm->msg == NULL ? "compressed data error" : strm->msg); + strm->msg == NULL ? "compressed data error" : strm->msg); return -1; } } while (strm->avail_out && ret != Z_STREAM_END); - /* update available output and crc check value */ - state->have = had - strm->avail_out; - state->next = strm->next_out - state->have; - strm->adler = crc32(strm->adler, state->next, state->have); + /* update available output */ + state->x.have = had - strm->avail_out; + state->x.next = strm->next_out - state->x.have; - /* check gzip trailer if at end of deflate stream */ - if (ret == Z_STREAM_END) { - if (gz_next4(state, &crc) == -1 || gz_next4(state, &len) == -1) { - gz_error(state, Z_DATA_ERROR, "unexpected end of file"); - return -1; - } - if (crc != strm->adler) { - gz_error(state, Z_DATA_ERROR, "incorrect data check"); - return -1; - } - if (len != (strm->total_out & 0xffffffffL)) { - gz_error(state, Z_DATA_ERROR, "incorrect length check"); - return -1; - } - state->how = LOOK; /* ready for next stream, once have is 0 (leave - state->direct unchanged to remember how) */ - } + /* if the gzip stream completed successfully, look for another */ + if (ret == Z_STREAM_END) + state->how = LOOK; /* good decompression */ return 0; } -/* Make data and put in the output buffer. Assumes that state->have == 0. +/* Fetch data and put it in the output buffer. Assumes state->x.have is 0. Data is either copied from the input file or decompressed from the input file depending on state->how. If state->how is LOOK, then a gzip header is - looked for (and skipped if found) to determine wither to copy or decompress. - Returns -1 on error, otherwise 0. gz_make() will leave state->have as COPY - or GZIP unless the end of the input file has been reached and all data has - been processed. */ -local int gz_make(state) + looked for to determine whether to copy or decompress. Returns -1 on error, + otherwise 0. gz_fetch() will leave state->how as COPY or GZIP unless the + end of the input file has been reached and all data has been processed. */ +local int gz_fetch(state) gz_statep state; { z_streamp strm = &(state->strm); - if (state->how == LOOK) { /* look for gzip header */ - if (gz_head(state) == -1) - return -1; - if (state->have) /* got some data from gz_head() */ + do { + switch(state->how) { + case LOOK: /* -> LOOK, COPY (only if never GZIP), or GZIP */ + if (gz_look(state) == -1) + return -1; + if (state->how == LOOK) + return 0; + break; + case COPY: /* -> COPY */ + if (gz_load(state, state->out, state->size << 1, &(state->x.have)) + == -1) + return -1; + state->x.next = state->out; return 0; - } - if (state->how == COPY) { /* straight copy */ - if (gz_load(state, state->out, state->size << 1, &(state->have)) == -1) - return -1; - state->next = state->out; - } - else if (state->how == GZIP) { /* decompress */ - strm->avail_out = state->size << 1; - strm->next_out = state->out; - if (gz_decomp(state) == -1) - return -1; - } + case GZIP: /* -> GZIP or LOOK (if end of gzip stream) */ + strm->avail_out = state->size << 1; + strm->next_out = state->out; + if (gz_decomp(state) == -1) + return -1; + } + } while (state->x.have == 0 && (!state->eof || strm->avail_in)); return 0; } @@ -336,12 +262,12 @@ local int gz_skip(state, len) /* skip over len bytes or reach end-of-file, whichever comes first */ while (len) /* skip over whatever is in output buffer */ - if (state->have) { - n = GT_OFF(state->have) || (z_off64_t)state->have > len ? - (unsigned)len : state->have; - state->have -= n; - state->next += n; - state->pos += n; + if (state->x.have) { + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > len ? + (unsigned)len : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; len -= n; } @@ -352,7 +278,7 @@ local int gz_skip(state, len) /* need more data to skip -- load up output buffer */ else { /* get more output, looking for header if required */ - if (gz_make(state) == -1) + if (gz_fetch(state) == -1) return -1; } return 0; @@ -374,14 +300,15 @@ int ZEXPORT gzread(file, buf, len) state = (gz_statep)file; strm = &(state->strm); - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* since an int is returned, make sure len fits in one, otherwise return with an error (this avoids the flaw in the interface) */ if ((int)len < 0) { - gz_error(state, Z_BUF_ERROR, "requested length does not fit in int"); + gz_error(state, Z_DATA_ERROR, "requested length does not fit in int"); return -1; } @@ -400,49 +327,51 @@ int ZEXPORT gzread(file, buf, len) got = 0; do { /* first just try copying data from the output buffer */ - if (state->have) { - n = state->have > len ? len : state->have; - memcpy(buf, state->next, n); - state->next += n; - state->have -= n; + if (state->x.have) { + n = state->x.have > len ? len : state->x.have; + memcpy(buf, state->x.next, n); + state->x.next += n; + state->x.have -= n; } /* output buffer empty -- return if we're at the end of the input */ - else if (state->eof && strm->avail_in == 0) + else if (state->eof && strm->avail_in == 0) { + state->past = 1; /* tried to read past end */ break; + } /* need output data -- for small len or new stream load up our output buffer */ else if (state->how == LOOK || len < (state->size << 1)) { /* get more output, looking for header if required */ - if (gz_make(state) == -1) + if (gz_fetch(state) == -1) return -1; - continue; /* no progress yet -- go back to memcpy() above */ + continue; /* no progress yet -- go back to copy above */ /* the copy above assures that we will leave with space in the output buffer, allowing at least one gzungetc() to succeed */ } /* large len -- read directly into user buffer */ else if (state->how == COPY) { /* read directly */ - if (gz_load(state, buf, len, &n) == -1) + if (gz_load(state, (unsigned char *)buf, len, &n) == -1) return -1; } /* large len -- decompress directly into user buffer */ else { /* state->how == GZIP */ strm->avail_out = len; - strm->next_out = buf; + strm->next_out = (unsigned char *)buf; if (gz_decomp(state) == -1) return -1; - n = state->have; - state->have = 0; + n = state->x.have; + state->x.have = 0; } /* update progress */ len -= n; buf = (char *)buf + n; got += n; - state->pos += n; + state->x.pos += n; } while (len); /* return number of bytes read into user buffer (will fit in int) */ @@ -450,6 +379,11 @@ int ZEXPORT gzread(file, buf, len) } /* -- see zlib.h -- */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +#else +# undef gzgetc +#endif int ZEXPORT gzgetc(file) gzFile file; { @@ -462,15 +396,16 @@ int ZEXPORT gzgetc(file) return -1; state = (gz_statep)file; - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* try output buffer (no need to check for skip request) */ - if (state->have) { - state->have--; - state->pos++; - return *(state->next)++; + if (state->x.have) { + state->x.have--; + state->x.pos++; + return *(state->x.next)++; } /* nothing there -- try gzread() */ @@ -478,6 +413,12 @@ int ZEXPORT gzgetc(file) return ret < 1 ? -1 : buf[0]; } +int ZEXPORT gzgetc_(file) +gzFile file; +{ + return gzgetc(file); +} + /* -- see zlib.h -- */ int ZEXPORT gzungetc(c, file) int c; @@ -490,8 +431,9 @@ int ZEXPORT gzungetc(c, file) return -1; state = (gz_statep)file; - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* process a skip request */ @@ -506,32 +448,34 @@ int ZEXPORT gzungetc(c, file) return -1; /* if output buffer empty, put byte at end (allows more pushing) */ - if (state->have == 0) { - state->have = 1; - state->next = state->out + (state->size << 1) - 1; - state->next[0] = c; - state->pos--; + if (state->x.have == 0) { + state->x.have = 1; + state->x.next = state->out + (state->size << 1) - 1; + state->x.next[0] = c; + state->x.pos--; + state->past = 0; return c; } /* if no room, give up (must have already done a gzungetc()) */ - if (state->have == (state->size << 1)) { - gz_error(state, Z_BUF_ERROR, "out of room to push characters"); + if (state->x.have == (state->size << 1)) { + gz_error(state, Z_DATA_ERROR, "out of room to push characters"); return -1; } /* slide output data if needed and insert byte before existing data */ - if (state->next == state->out) { - unsigned char *src = state->out + state->have; + if (state->x.next == state->out) { + unsigned char *src = state->out + state->x.have; unsigned char *dest = state->out + (state->size << 1); while (src > state->out) *--dest = *--src; - state->next = dest; + state->x.next = dest; } - state->have++; - state->next--; - state->next[0] = c; - state->pos--; + state->x.have++; + state->x.next--; + state->x.next[0] = c; + state->x.pos--; + state->past = 0; return c; } @@ -551,8 +495,9 @@ char * ZEXPORT gzgets(file, buf, len) return NULL; state = (gz_statep)file; - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return NULL; /* process a skip request */ @@ -569,32 +514,31 @@ char * ZEXPORT gzgets(file, buf, len) left = (unsigned)len - 1; if (left) do { /* assure that something is in the output buffer */ - if (state->have == 0) { - if (gz_make(state) == -1) - return NULL; /* error */ - if (state->have == 0) { /* end of file */ - if (buf == str) /* got bupkus */ - return NULL; - break; /* got something -- return it */ - } + if (state->x.have == 0 && gz_fetch(state) == -1) + return NULL; /* error */ + if (state->x.have == 0) { /* end of file */ + state->past = 1; /* read past end */ + break; /* return what we have */ } /* look for end-of-line in current output buffer */ - n = state->have > left ? left : state->have; - eol = memchr(state->next, '\n', n); + n = state->x.have > left ? left : state->x.have; + eol = (unsigned char *)memchr(state->x.next, '\n', n); if (eol != NULL) - n = (unsigned)(eol - state->next) + 1; + n = (unsigned)(eol - state->x.next) + 1; /* copy through end-of-line, or remainder if not found */ - memcpy(buf, state->next, n); - state->have -= n; - state->next += n; - state->pos += n; + memcpy(buf, state->x.next, n); + state->x.have -= n; + state->x.next += n; + state->x.pos += n; left -= n; buf += n; } while (left && eol == NULL); - /* found end-of-line or out of space -- terminate string and return it */ + /* return terminated string, or if nothing, end of file */ + if (buf == str) + return NULL; buf[0] = 0; return str; } @@ -610,16 +554,12 @@ int ZEXPORT gzdirect(file) return 0; state = (gz_statep)file; - /* check that we're reading */ - if (state->mode != GZ_READ) - return 0; - /* if the state is not known, but we can find out, then do so (this is mainly for right after a gzopen() or gzdopen()) */ - if (state->how == LOOK && state->have == 0) - (void)gz_head(state); + if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0) + (void)gz_look(state); - /* return 1 if reading direct, 0 if decompressing a gzip stream */ + /* return 1 if transparent, 0 if processing a gzip stream */ return state->direct; } @@ -627,7 +567,7 @@ int ZEXPORT gzdirect(file) int ZEXPORT gzclose_r(file) gzFile file; { - int ret; + int ret, err; gz_statep state; /* get internal structure */ @@ -645,9 +585,10 @@ int ZEXPORT gzclose_r(file) free(state->out); free(state->in); } + err = state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK; gz_error(state, Z_OK, NULL); free(state->path); ret = close(state->fd); free(state); - return ret ? Z_ERRNO : Z_OK; + return ret ? Z_ERRNO : err; } diff --git a/etc/c/zlib/gzwrite.c b/etc/c/zlib/gzwrite.c index e8defc6887a..aa767fbf63e 100644 --- a/etc/c/zlib/gzwrite.c +++ b/etc/c/zlib/gzwrite.c @@ -1,5 +1,5 @@ /* gzwrite.c -- zlib functions for writing gzip files - * Copyright (C) 2004, 2005, 2010 Mark Adler + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -18,44 +18,55 @@ local int gz_init(state) int ret; z_streamp strm = &(state->strm); - /* allocate input and output buffers */ - state->in = malloc(state->want); - state->out = malloc(state->want); - if (state->in == NULL || state->out == NULL) { - if (state->out != NULL) - free(state->out); - if (state->in != NULL) - free(state->in); + /* allocate input buffer */ + state->in = (unsigned char *)malloc(state->want); + if (state->in == NULL) { gz_error(state, Z_MEM_ERROR, "out of memory"); return -1; } - /* allocate deflate memory, set up for gzip compression */ - strm->zalloc = Z_NULL; - strm->zfree = Z_NULL; - strm->opaque = Z_NULL; - ret = deflateInit2(strm, state->level, Z_DEFLATED, - 15 + 16, 8, state->strategy); - if (ret != Z_OK) { - free(state->in); - gz_error(state, Z_MEM_ERROR, "out of memory"); - return -1; + /* only need output buffer and deflate state if compressing */ + if (!state->direct) { + /* allocate output buffer */ + state->out = (unsigned char *)malloc(state->want); + if (state->out == NULL) { + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + + /* allocate deflate memory, set up for gzip compression */ + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + ret = deflateInit2(strm, state->level, Z_DEFLATED, + MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy); + if (ret != Z_OK) { + free(state->out); + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } } /* mark state as initialized */ state->size = state->want; - /* initialize write buffer */ - strm->avail_out = state->size; - strm->next_out = state->out; - state->next = strm->next_out; + /* initialize write buffer if compressing */ + if (!state->direct) { + strm->avail_out = state->size; + strm->next_out = state->out; + state->x.next = strm->next_out; + } return 0; } /* Compress whatever is at avail_in and next_in and write to the output file. Return -1 if there is an error writing to the output file, otherwise 0. flush is assumed to be a valid deflate() flush value. If flush is Z_FINISH, - then the deflate() state is reset to start a new gzip stream. */ + then the deflate() state is reset to start a new gzip stream. If gz->direct + is true, then simply write to the output file without compressing, and + ignore flush. */ local int gz_comp(state, flush) gz_statep state; int flush; @@ -68,6 +79,17 @@ local int gz_comp(state, flush) if (state->size == 0 && gz_init(state) == -1) return -1; + /* write directly if requested */ + if (state->direct) { + got = write(state->fd, strm->next_in, strm->avail_in); + if (got < 0 || (unsigned)got != strm->avail_in) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + strm->avail_in = 0; + return 0; + } + /* run deflate() on provided input until it produces no more output */ ret = Z_OK; do { @@ -75,8 +97,8 @@ local int gz_comp(state, flush) doing Z_FINISH then don't write until we get to Z_STREAM_END */ if (strm->avail_out == 0 || (flush != Z_NO_FLUSH && (flush != Z_FINISH || ret == Z_STREAM_END))) { - have = (unsigned)(strm->next_out - state->next); - if (have && ((got = write(state->fd, state->next, have)) < 0 || + have = (unsigned)(strm->next_out - state->x.next); + if (have && ((got = write(state->fd, state->x.next, have)) < 0 || (unsigned)got != have)) { gz_error(state, Z_ERRNO, zstrerror()); return -1; @@ -85,7 +107,7 @@ local int gz_comp(state, flush) strm->avail_out = state->size; strm->next_out = state->out; } - state->next = strm->next_out; + state->x.next = strm->next_out; } /* compress */ @@ -131,7 +153,7 @@ local int gz_zero(state, len) } strm->avail_in = n; strm->next_in = state->in; - state->pos += n; + state->x.pos += n; if (gz_comp(state, Z_NO_FLUSH) == -1) return -1; len -= n; @@ -146,7 +168,6 @@ int ZEXPORT gzwrite(file, buf, len) unsigned len; { unsigned put = len; - unsigned n; gz_statep state; z_streamp strm; @@ -163,7 +184,7 @@ int ZEXPORT gzwrite(file, buf, len) /* since an int is returned, make sure len fits in one, otherwise return with an error (this avoids the flaw in the interface) */ if ((int)len < 0) { - gz_error(state, Z_BUF_ERROR, "requested length does not fit in int"); + gz_error(state, Z_DATA_ERROR, "requested length does not fit in int"); return 0; } @@ -186,16 +207,19 @@ int ZEXPORT gzwrite(file, buf, len) if (len < state->size) { /* copy to input buffer, compress when full */ do { + unsigned have, copy; + if (strm->avail_in == 0) strm->next_in = state->in; - n = state->size - strm->avail_in; - if (n > len) - n = len; - memcpy(strm->next_in + strm->avail_in, buf, n); - strm->avail_in += n; - state->pos += n; - buf = (char *)buf + n; - len -= n; + have = (unsigned)((strm->next_in + strm->avail_in) - state->in); + copy = state->size - have; + if (copy > len) + copy = len; + memcpy(state->in + have, buf, copy); + strm->avail_in += copy; + state->x.pos += copy; + buf = (const char *)buf + copy; + len -= copy; if (len && gz_comp(state, Z_NO_FLUSH) == -1) return 0; } while (len); @@ -207,8 +231,8 @@ int ZEXPORT gzwrite(file, buf, len) /* directly compress user buffer to file */ strm->avail_in = len; - strm->next_in = (voidp)buf; - state->pos += len; + strm->next_in = (z_const Bytef *)buf; + state->x.pos += len; if (gz_comp(state, Z_NO_FLUSH) == -1) return 0; } @@ -222,6 +246,7 @@ int ZEXPORT gzputc(file, c) gzFile file; int c; { + unsigned have; unsigned char buf[1]; gz_statep state; z_streamp strm; @@ -245,19 +270,23 @@ int ZEXPORT gzputc(file, c) /* try writing to input buffer for speed (state->size == 0 if buffer not initialized) */ - if (strm->avail_in < state->size) { + if (state->size) { if (strm->avail_in == 0) strm->next_in = state->in; - strm->next_in[strm->avail_in++] = c; - state->pos++; - return c; + have = (unsigned)((strm->next_in + strm->avail_in) - state->in); + if (have < state->size) { + state->in[have] = c; + strm->avail_in++; + state->x.pos++; + return c & 0xff; + } } /* no room in buffer or not initialized, use gz_write() */ buf[0] = c; if (gzwrite(file, buf, 1) != 1) return -1; - return c; + return c & 0xff; } /* -- see zlib.h -- */ @@ -274,16 +303,15 @@ int ZEXPORT gzputs(file, str) return ret == 0 && len != 0 ? -1 : ret; } -#ifdef STDC +#if defined(STDC) || defined(Z_HAVE_STDARG_H) #include /* -- see zlib.h -- */ -int ZEXPORTVA gzprintf (gzFile file, const char *format, ...) +int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) { int size, len; gz_statep state; z_streamp strm; - va_list va; /* get internal structure */ if (file == NULL) @@ -313,25 +341,20 @@ int ZEXPORTVA gzprintf (gzFile file, const char *format, ...) /* do the printf() into the input buffer, put length in len */ size = (int)(state->size); state->in[size - 1] = 0; - va_start(va, format); #ifdef NO_vsnprintf # ifdef HAS_vsprintf_void - (void)vsprintf(state->in, format, va); - va_end(va); + (void)vsprintf((char *)(state->in), format, va); for (len = 0; len < size; len++) if (state->in[len] == 0) break; # else - len = vsprintf(state->in, format, va); - va_end(va); + len = vsprintf((char *)(state->in), format, va); # endif #else # ifdef HAS_vsnprintf_void - (void)vsnprintf(state->in, size, format, va); - va_end(va); - len = strlen(state->in); + (void)vsnprintf((char *)(state->in), size, format, va); + len = strlen((char *)(state->in)); # else len = vsnprintf((char *)(state->in), size, format, va); - va_end(va); # endif #endif @@ -342,11 +365,22 @@ int ZEXPORTVA gzprintf (gzFile file, const char *format, ...) /* update buffer and position, defer compression until needed */ strm->avail_in = (unsigned)len; strm->next_in = state->in; - state->pos += len; + state->x.pos += len; return len; } -#else /* !STDC */ +int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) +{ + va_list va; + int ret; + + va_start(va, format); + ret = gzvprintf(file, format, va); + va_end(va); + return ret; +} + +#else /* !STDC && !Z_HAVE_STDARG_H */ /* -- see zlib.h -- */ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, @@ -366,6 +400,10 @@ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, state = (gz_statep)file; strm = &(state->strm); + /* check that can really pass pointer in ints */ + if (sizeof(int) != sizeof(void *)) + return 0; + /* check that we're writing and that there's no error */ if (state->mode != GZ_WRITE || state->err != Z_OK) return 0; @@ -390,22 +428,23 @@ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, state->in[size - 1] = 0; #ifdef NO_snprintf # ifdef HAS_sprintf_void - sprintf(state->in, format, a1, a2, a3, a4, a5, a6, a7, a8, + sprintf((char *)(state->in), format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); for (len = 0; len < size; len++) if (state->in[len] == 0) break; # else - len = sprintf(state->in, format, a1, a2, a3, a4, a5, a6, a7, a8, - a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + len = sprintf((char *)(state->in), format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); # endif #else # ifdef HAS_snprintf_void - snprintf(state->in, size, format, a1, a2, a3, a4, a5, a6, a7, a8, + snprintf((char *)(state->in), size, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); - len = strlen(state->in); + len = strlen((char *)(state->in)); # else - len = snprintf(state->in, size, format, a1, a2, a3, a4, a5, a6, a7, a8, - a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + len = snprintf((char *)(state->in), size, format, a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20); # endif #endif @@ -416,7 +455,7 @@ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, /* update buffer and position, defer compression until needed */ strm->avail_in = (unsigned)len; strm->next_in = state->in; - state->pos += len; + state->x.pos += len; return len; } @@ -500,7 +539,7 @@ int ZEXPORT gzsetparams(file, level, strategy) int ZEXPORT gzclose_w(file) gzFile file; { - int ret = 0; + int ret = Z_OK; gz_statep state; /* get internal structure */ @@ -515,17 +554,24 @@ int ZEXPORT gzclose_w(file) /* check for seek request */ if (state->seek) { state->seek = 0; - ret += gz_zero(state, state->skip); + if (gz_zero(state, state->skip) == -1) + ret = state->err; } /* flush, free memory, and close file */ - ret += gz_comp(state, Z_FINISH); - (void)deflateEnd(&(state->strm)); - free(state->out); - free(state->in); + if (gz_comp(state, Z_FINISH) == -1) + ret = state->err; + if (state->size) { + if (!state->direct) { + (void)deflateEnd(&(state->strm)); + free(state->out); + } + free(state->in); + } gz_error(state, Z_OK, NULL); free(state->path); - ret += close(state->fd); + if (close(state->fd) == -1) + ret = Z_ERRNO; free(state); - return ret ? Z_ERRNO : Z_OK; + return ret; } diff --git a/etc/c/zlib/infback.c b/etc/c/zlib/infback.c index af3a8c965d5..f3833c2e434 100644 --- a/etc/c/zlib/infback.c +++ b/etc/c/zlib/infback.c @@ -1,5 +1,5 @@ /* infback.c -- inflate using a call-back interface - * Copyright (C) 1995-2009 Mark Adler + * Copyright (C) 1995-2011 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -42,10 +42,19 @@ int stream_size; return Z_STREAM_ERROR; strm->msg = Z_NULL; /* in case we return an error */ if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else strm->zalloc = zcalloc; strm->opaque = (voidpf)0; +#endif } - if (strm->zfree == (free_func)0) strm->zfree = zcfree; + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif state = (struct inflate_state FAR *)ZALLOC(strm, 1, sizeof(struct inflate_state)); if (state == Z_NULL) return Z_MEM_ERROR; @@ -246,7 +255,7 @@ out_func out; void FAR *out_desc; { struct inflate_state FAR *state; - unsigned char FAR *next; /* next input */ + z_const unsigned char FAR *next; /* next input */ unsigned char FAR *put; /* next output */ unsigned have, left; /* available input and output */ unsigned long hold; /* bit buffer */ @@ -394,7 +403,6 @@ void FAR *out_desc; PULLBYTE(); } if (here.val < 16) { - NEEDBITS(here.bits); DROPBITS(here.bits); state->lens[state->have++] = here.val; } diff --git a/etc/c/zlib/inffast.c b/etc/c/zlib/inffast.c index 2f1d60b43b8..bda59ceb6a1 100644 --- a/etc/c/zlib/inffast.c +++ b/etc/c/zlib/inffast.c @@ -1,5 +1,5 @@ /* inffast.c -- fast decoding - * Copyright (C) 1995-2008, 2010 Mark Adler + * Copyright (C) 1995-2008, 2010, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -69,8 +69,8 @@ z_streamp strm; unsigned start; /* inflate()'s starting value for strm->avail_out */ { struct inflate_state FAR *state; - unsigned char FAR *in; /* local strm->next_in */ - unsigned char FAR *last; /* while in < last, enough input available */ + z_const unsigned char FAR *in; /* local strm->next_in */ + z_const unsigned char FAR *last; /* have enough input while in < last */ unsigned char FAR *out; /* local strm->next_out */ unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ unsigned char FAR *end; /* while out < end, enough space available */ diff --git a/etc/c/zlib/inffixed.h b/etc/c/zlib/inffixed.h index 75ed4b5978d..d6283277694 100644 --- a/etc/c/zlib/inffixed.h +++ b/etc/c/zlib/inffixed.h @@ -2,9 +2,9 @@ * Generated automatically by makefixed(). */ - /* WARNING: this file should *not* be used by applications. It - is part of the implementation of the compression library and - is subject to change. Applications should only use zlib.h. + /* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. */ static const code lenfix[512] = { diff --git a/etc/c/zlib/inflate.c b/etc/c/zlib/inflate.c index a8431abeacf..870f89bb4d3 100644 --- a/etc/c/zlib/inflate.c +++ b/etc/c/zlib/inflate.c @@ -1,5 +1,5 @@ /* inflate.c -- zlib decompression - * Copyright (C) 1995-2010 Mark Adler + * Copyright (C) 1995-2012 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -93,14 +93,15 @@ /* function prototypes */ local void fixedtables OF((struct inflate_state FAR *state)); -local int updatewindow OF((z_streamp strm, unsigned out)); +local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, + unsigned copy)); #ifdef BUILDFIXED void makefixed OF((void)); #endif -local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf, +local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, unsigned len)); -int ZEXPORT inflateReset(strm) +int ZEXPORT inflateResetKeep(strm) z_streamp strm; { struct inflate_state FAR *state; @@ -109,15 +110,13 @@ z_streamp strm; state = (struct inflate_state FAR *)strm->state; strm->total_in = strm->total_out = state->total = 0; strm->msg = Z_NULL; - strm->adler = 1; /* to support ill-conceived Java test suite */ + if (state->wrap) /* to support ill-conceived Java test suite */ + strm->adler = state->wrap & 1; state->mode = HEAD; state->last = 0; state->havedict = 0; state->dmax = 32768U; state->head = Z_NULL; - state->wsize = 0; - state->whave = 0; - state->wnext = 0; state->hold = 0; state->bits = 0; state->lencode = state->distcode = state->next = state->codes; @@ -127,6 +126,19 @@ z_streamp strm; return Z_OK; } +int ZEXPORT inflateReset(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + state->wsize = 0; + state->whave = 0; + state->wnext = 0; + return inflateResetKeep(strm); +} + int ZEXPORT inflateReset2(strm, windowBits) z_streamp strm; int windowBits; @@ -180,10 +192,19 @@ int stream_size; if (strm == Z_NULL) return Z_STREAM_ERROR; strm->msg = Z_NULL; /* in case we return an error */ if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else strm->zalloc = zcalloc; strm->opaque = (voidpf)0; +#endif } - if (strm->zfree == (free_func)0) strm->zfree = zcfree; + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif state = (struct inflate_state FAR *) ZALLOC(strm, 1, sizeof(struct inflate_state)); if (state == Z_NULL) return Z_MEM_ERROR; @@ -321,8 +342,8 @@ void makefixed() low = 0; for (;;) { if ((low % 7) == 0) printf("\n "); - printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits, - state.lencode[low].val); + printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op, + state.lencode[low].bits, state.lencode[low].val); if (++low == size) break; putchar(','); } @@ -355,12 +376,13 @@ void makefixed() output will fall in the output data, making match copies simpler and faster. The advantage may be dependent on the size of the processor's data caches. */ -local int updatewindow(strm, out) +local int updatewindow(strm, end, copy) z_streamp strm; -unsigned out; +const Bytef *end; +unsigned copy; { struct inflate_state FAR *state; - unsigned copy, dist; + unsigned dist; state = (struct inflate_state FAR *)strm->state; @@ -380,19 +402,18 @@ unsigned out; } /* copy state->wsize or less output bytes into the circular window */ - copy = out - strm->avail_out; if (copy >= state->wsize) { - zmemcpy(state->window, strm->next_out - state->wsize, state->wsize); + zmemcpy(state->window, end - state->wsize, state->wsize); state->wnext = 0; state->whave = state->wsize; } else { dist = state->wsize - state->wnext; if (dist > copy) dist = copy; - zmemcpy(state->window + state->wnext, strm->next_out - copy, dist); + zmemcpy(state->window + state->wnext, end - copy, dist); copy -= dist; if (copy) { - zmemcpy(state->window, strm->next_out - copy, copy); + zmemcpy(state->window, end - copy, copy); state->wnext = copy; state->whave = state->wsize; } @@ -499,11 +520,6 @@ unsigned out; bits -= bits & 7; \ } while (0) -/* Reverse the bytes in a 32-bit value */ -#define REVERSE(q) \ - ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ - (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) - /* inflate() uses a state machine to process as much input data and generate as much output data as possible before returning. The state machine is @@ -591,7 +607,7 @@ z_streamp strm; int flush; { struct inflate_state FAR *state; - unsigned char FAR *next; /* next input */ + z_const unsigned char FAR *next; /* next input */ unsigned char FAR *put; /* next output */ unsigned have, left; /* available input and output */ unsigned long hold; /* bit buffer */ @@ -797,7 +813,7 @@ int flush; #endif case DICTID: NEEDBITS(32); - strm->adler = state->check = REVERSE(hold); + strm->adler = state->check = ZSWAP32(hold); INITBITS(); state->mode = DICT; case DICT: @@ -905,7 +921,7 @@ int flush; while (state->have < 19) state->lens[order[state->have++]] = 0; state->next = state->codes; - state->lencode = (code const FAR *)(state->next); + state->lencode = (const code FAR *)(state->next); state->lenbits = 7; ret = inflate_table(CODES, state->lens, 19, &(state->next), &(state->lenbits), state->work); @@ -925,7 +941,6 @@ int flush; PULLBYTE(); } if (here.val < 16) { - NEEDBITS(here.bits); DROPBITS(here.bits); state->lens[state->have++] = here.val; } @@ -980,7 +995,7 @@ int flush; values here (9 and 6) without reading the comments in inftrees.h concerning the ENOUGH constants, which depend on those values */ state->next = state->codes; - state->lencode = (code const FAR *)(state->next); + state->lencode = (const code FAR *)(state->next); state->lenbits = 9; ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), &(state->lenbits), state->work); @@ -989,7 +1004,7 @@ int flush; state->mode = BAD; break; } - state->distcode = (code const FAR *)(state->next); + state->distcode = (const code FAR *)(state->next); state->distbits = 6; ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, &(state->next), &(state->distbits), state->work); @@ -1170,7 +1185,7 @@ int flush; #ifdef GUNZIP state->flags ? hold : #endif - REVERSE(hold)) != state->check) { + ZSWAP32(hold)) != state->check) { strm->msg = (char *)"incorrect data check"; state->mode = BAD; break; @@ -1214,8 +1229,9 @@ int flush; */ inf_leave: RESTORE(); - if (state->wsize || (state->mode < CHECK && out != strm->avail_out)) - if (updatewindow(strm, out)) { + if (state->wsize || (out != strm->avail_out && state->mode < BAD && + (state->mode < CHECK || flush != Z_FINISH))) + if (updatewindow(strm, strm->next_out, out - strm->avail_out)) { state->mode = MEM; return Z_MEM_ERROR; } @@ -1249,13 +1265,37 @@ z_streamp strm; return Z_OK; } +int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength) +z_streamp strm; +Bytef *dictionary; +uInt *dictLength; +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* copy dictionary */ + if (state->whave && dictionary != Z_NULL) { + zmemcpy(dictionary, state->window + state->wnext, + state->whave - state->wnext); + zmemcpy(dictionary + state->whave - state->wnext, + state->window, state->wnext); + } + if (dictLength != Z_NULL) + *dictLength = state->whave; + return Z_OK; +} + int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) z_streamp strm; const Bytef *dictionary; uInt dictLength; { struct inflate_state FAR *state; - unsigned long id; + unsigned long dictid; + int ret; /* check state */ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; @@ -1263,29 +1303,21 @@ uInt dictLength; if (state->wrap != 0 && state->mode != DICT) return Z_STREAM_ERROR; - /* check for correct dictionary id */ + /* check for correct dictionary identifier */ if (state->mode == DICT) { - id = adler32(0L, Z_NULL, 0); - id = adler32(id, dictionary, dictLength); - if (id != state->check) + dictid = adler32(0L, Z_NULL, 0); + dictid = adler32(dictid, dictionary, dictLength); + if (dictid != state->check) return Z_DATA_ERROR; } - /* copy dictionary to window */ - if (updatewindow(strm, strm->avail_out)) { + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary + dictLength, dictLength); + if (ret) { state->mode = MEM; return Z_MEM_ERROR; } - if (dictLength > state->wsize) { - zmemcpy(state->window, dictionary + dictLength - state->wsize, - state->wsize); - state->whave = state->wsize; - } - else { - zmemcpy(state->window + state->wsize - dictLength, dictionary, - dictLength); - state->whave = dictLength; - } state->havedict = 1; Tracev((stderr, "inflate: dictionary set\n")); return Z_OK; @@ -1321,7 +1353,7 @@ gz_headerp head; */ local unsigned syncsearch(have, buf, len) unsigned FAR *have; -unsigned char FAR *buf; +const unsigned char FAR *buf; unsigned len; { unsigned got; @@ -1433,8 +1465,8 @@ z_streamp source; } /* copy state */ - zmemcpy(dest, source, sizeof(z_stream)); - zmemcpy(copy, state, sizeof(struct inflate_state)); + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state)); if (state->lencode >= state->codes && state->lencode <= state->codes + ENOUGH - 1) { copy->lencode = copy->codes + (state->lencode - state->codes); diff --git a/etc/c/zlib/inftrees.c b/etc/c/zlib/inftrees.c index 11e9c52accb..44d89cf24e1 100644 --- a/etc/c/zlib/inftrees.c +++ b/etc/c/zlib/inftrees.c @@ -1,5 +1,5 @@ /* inftrees.c -- generate Huffman trees for efficient decoding - * Copyright (C) 1995-2010 Mark Adler + * Copyright (C) 1995-2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -9,7 +9,7 @@ #define MAXBITS 15 const char inflate_copyright[] = - " inflate 1.2.5 Copyright 1995-2010 Mark Adler "; + " inflate 1.2.8 Copyright 1995-2013 Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -62,7 +62,7 @@ unsigned short FAR *work; 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; static const unsigned short lext[31] = { /* Length codes 257..285 extra */ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 73, 195}; + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78}; static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, @@ -208,8 +208,8 @@ unsigned short FAR *work; mask = used - 1; /* mask for comparing low */ /* check available table space */ - if ((type == LENS && used >= ENOUGH_LENS) || - (type == DISTS && used >= ENOUGH_DISTS)) + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) return 1; /* process all codes and make table entries */ @@ -277,8 +277,8 @@ unsigned short FAR *work; /* check for enough space */ used += 1U << curr; - if ((type == LENS && used >= ENOUGH_LENS) || - (type == DISTS && used >= ENOUGH_DISTS)) + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) return 1; /* point entry in root table to sub-table */ @@ -289,38 +289,14 @@ unsigned short FAR *work; } } - /* - Fill in rest of table for incomplete codes. This loop is similar to the - loop above in incrementing huff for table indices. It is assumed that - len is equal to curr + drop, so there is no loop needed to increment - through high index bits. When the current sub-table is filled, the loop - drops back to the root table to fill in any remaining entries there. - */ - here.op = (unsigned char)64; /* invalid code marker */ - here.bits = (unsigned char)(len - drop); - here.val = (unsigned short)0; - while (huff != 0) { - /* when done with sub-table, drop back to root table */ - if (drop != 0 && (huff & mask) != low) { - drop = 0; - len = root; - next = *table; - here.bits = (unsigned char)len; - } - - /* put invalid code marker in table */ - next[huff >> drop] = here; - - /* backwards increment the len-bit code huff */ - incr = 1U << (len - 1); - while (huff & incr) - incr >>= 1; - if (incr != 0) { - huff &= incr - 1; - huff += incr; - } - else - huff = 0; + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff != 0) { + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)(len - drop); + here.val = (unsigned short)0; + next[huff] = here; } /* set return parameters */ diff --git a/etc/c/zlib/linux.mak b/etc/c/zlib/linux.mak index 604f70027c6..6faafaa1836 100644 --- a/etc/c/zlib/linux.mak +++ b/etc/c/zlib/linux.mak @@ -1,95 +1,95 @@ -# Makefile for zlib - -MODEL=32 -CC=gcc -LD=link -CFLAGS=-O -m$(MODEL) -LDFLAGS= -O=.o - -.c.o: - $(CC) -c $(CFLAGS) $* - -.d.o: - $(DMD) -c $(DFLAGS) $* - -# variables -OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ - gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) - -all: zlib.a example minigzip - -adler32.o: zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -zutil.o: zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -gzclose.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzlib.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzread.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzwrite.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -compress.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -example.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -minigzip.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -uncompr.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -crc32.o: zutil.h zlib.h zconf.h crc32.h - $(CC) -c $(CFLAGS) $*.c - -deflate.o: deflate.h zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) -c $(CFLAGS) $*.c - -inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) -c $(CFLAGS) $*.c - -inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h - $(CC) -c $(CFLAGS) $*.c - -inftrees.o: zutil.h zlib.h zconf.h inftrees.h - $(CC) -c $(CFLAGS) $*.c - -trees.o: deflate.h zutil.h zlib.h zconf.h trees.h - $(CC) -c $(CFLAGS) $*.c - - -example.o: example.c zlib.h zconf.h - $(CC) -c $(cvarsdll) $(CFLAGS) $*.c - -minigzip.o: minigzip.c zlib.h zconf.h - $(CC) -c $(cvarsdll) $(CFLAGS) $*.c - -zlib.a: $(OBJS) - ar -r $@ $(OBJS) - -example: example.o zlib.a - $(CC) $(CFLAGS) -o $@ example.o zlib.a -g - -minigzip: minigzip.o zlib.a - $(CC) $(CFLAGS) -o $@ minigzip.o zlib.a -g - -test: example minigzip - ./example - echo hello world | minigzip | minigzip -d - -clean: - $(RM) $(OBJS) zlib.a example.o example minigzip minigzip.o test foo.gz - +# Makefile for zlib + +MODEL=32 +CC=gcc +LD=link +CFLAGS=-O -m$(MODEL) +LDFLAGS= +O=.o + +.c.o: + $(CC) -c $(CFLAGS) $* + +.d.o: + $(DMD) -c $(DFLAGS) $* + +# variables +OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ + gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) + +all: zlib.a example minigzip + +adler32.o: zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +zutil.o: zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +gzclose.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzlib.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzread.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzwrite.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +compress.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +example.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +minigzip.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +uncompr.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +crc32.o: zutil.h zlib.h zconf.h crc32.h + $(CC) -c $(CFLAGS) $*.c + +deflate.o: deflate.h zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) -c $(CFLAGS) $*.c + +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) -c $(CFLAGS) $*.c + +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h + $(CC) -c $(CFLAGS) $*.c + +inftrees.o: zutil.h zlib.h zconf.h inftrees.h + $(CC) -c $(CFLAGS) $*.c + +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h + $(CC) -c $(CFLAGS) $*.c + + +example.o: example.c zlib.h zconf.h + $(CC) -c $(cvarsdll) $(CFLAGS) $*.c + +minigzip.o: minigzip.c zlib.h zconf.h + $(CC) -c $(cvarsdll) $(CFLAGS) $*.c + +zlib.a: $(OBJS) + ar -r $@ $(OBJS) + +example: example.o zlib.a + $(CC) $(CFLAGS) -o $@ example.o zlib.a -g + +minigzip: minigzip.o zlib.a + $(CC) $(CFLAGS) -o $@ minigzip.o zlib.a -g + +test: example minigzip + ./example + echo hello world | minigzip | minigzip -d + +clean: + $(RM) $(OBJS) zlib.a example.o example minigzip minigzip.o test foo.gz + diff --git a/etc/c/zlib/minigzip.c b/etc/c/zlib/minigzip.c index 9825ccc3a71..b3025a489a9 100644 --- a/etc/c/zlib/minigzip.c +++ b/etc/c/zlib/minigzip.c @@ -1,5 +1,5 @@ /* minigzip.c -- simulate gzip using the zlib compression library - * Copyright (C) 1995-2006, 2010 Jean-loup Gailly. + * Copyright (C) 1995-2006, 2010, 2011 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -40,6 +40,10 @@ # define SET_BINARY_MODE(file) #endif +#ifdef _MSC_VER +# define snprintf _snprintf +#endif + #ifdef VMS # define unlink delete # define GZ_SUFFIX "-gz" @@ -138,6 +142,197 @@ static void pwinerror (s) # define local #endif +#ifdef Z_SOLO +/* for Z_SOLO, create simplified gz* functions using deflate and inflate */ + +#if defined(Z_HAVE_UNISTD_H) || defined(Z_LARGE) +# include /* for unlink() */ +#endif + +void *myalloc OF((void *, unsigned, unsigned)); +void myfree OF((void *, void *)); + +void *myalloc(q, n, m) + void *q; + unsigned n, m; +{ + q = Z_NULL; + return calloc(n, m); +} + +void myfree(q, p) + void *q, *p; +{ + q = Z_NULL; + free(p); +} + +typedef struct gzFile_s { + FILE *file; + int write; + int err; + char *msg; + z_stream strm; +} *gzFile; + +gzFile gzopen OF((const char *, const char *)); +gzFile gzdopen OF((int, const char *)); +gzFile gz_open OF((const char *, int, const char *)); + +gzFile gzopen(path, mode) +const char *path; +const char *mode; +{ + return gz_open(path, -1, mode); +} + +gzFile gzdopen(fd, mode) +int fd; +const char *mode; +{ + return gz_open(NULL, fd, mode); +} + +gzFile gz_open(path, fd, mode) + const char *path; + int fd; + const char *mode; +{ + gzFile gz; + int ret; + + gz = malloc(sizeof(struct gzFile_s)); + if (gz == NULL) + return NULL; + gz->write = strchr(mode, 'w') != NULL; + gz->strm.zalloc = myalloc; + gz->strm.zfree = myfree; + gz->strm.opaque = Z_NULL; + if (gz->write) + ret = deflateInit2(&(gz->strm), -1, 8, 15 + 16, 8, 0); + else { + gz->strm.next_in = 0; + gz->strm.avail_in = Z_NULL; + ret = inflateInit2(&(gz->strm), 15 + 16); + } + if (ret != Z_OK) { + free(gz); + return NULL; + } + gz->file = path == NULL ? fdopen(fd, gz->write ? "wb" : "rb") : + fopen(path, gz->write ? "wb" : "rb"); + if (gz->file == NULL) { + gz->write ? deflateEnd(&(gz->strm)) : inflateEnd(&(gz->strm)); + free(gz); + return NULL; + } + gz->err = 0; + gz->msg = ""; + return gz; +} + +int gzwrite OF((gzFile, const void *, unsigned)); + +int gzwrite(gz, buf, len) + gzFile gz; + const void *buf; + unsigned len; +{ + z_stream *strm; + unsigned char out[BUFLEN]; + + if (gz == NULL || !gz->write) + return 0; + strm = &(gz->strm); + strm->next_in = (void *)buf; + strm->avail_in = len; + do { + strm->next_out = out; + strm->avail_out = BUFLEN; + (void)deflate(strm, Z_NO_FLUSH); + fwrite(out, 1, BUFLEN - strm->avail_out, gz->file); + } while (strm->avail_out == 0); + return len; +} + +int gzread OF((gzFile, void *, unsigned)); + +int gzread(gz, buf, len) + gzFile gz; + void *buf; + unsigned len; +{ + int ret; + unsigned got; + unsigned char in[1]; + z_stream *strm; + + if (gz == NULL || gz->write) + return 0; + if (gz->err) + return 0; + strm = &(gz->strm); + strm->next_out = (void *)buf; + strm->avail_out = len; + do { + got = fread(in, 1, 1, gz->file); + if (got == 0) + break; + strm->next_in = in; + strm->avail_in = 1; + ret = inflate(strm, Z_NO_FLUSH); + if (ret == Z_DATA_ERROR) { + gz->err = Z_DATA_ERROR; + gz->msg = strm->msg; + return 0; + } + if (ret == Z_STREAM_END) + inflateReset(strm); + } while (strm->avail_out); + return len - strm->avail_out; +} + +int gzclose OF((gzFile)); + +int gzclose(gz) + gzFile gz; +{ + z_stream *strm; + unsigned char out[BUFLEN]; + + if (gz == NULL) + return Z_STREAM_ERROR; + strm = &(gz->strm); + if (gz->write) { + strm->next_in = Z_NULL; + strm->avail_in = 0; + do { + strm->next_out = out; + strm->avail_out = BUFLEN; + (void)deflate(strm, Z_FINISH); + fwrite(out, 1, BUFLEN - strm->avail_out, gz->file); + } while (strm->avail_out == 0); + deflateEnd(strm); + } + else + inflateEnd(strm); + fclose(gz->file); + free(gz); + return Z_OK; +} + +const char *gzerror OF((gzFile, int *)); + +const char *gzerror(gz, err) + gzFile gz; + int *err; +{ + *err = gz->err; + return gz->msg; +} + +#endif + char *prog; void error OF((const char *msg)); @@ -272,8 +467,12 @@ void file_compress(file, mode) exit(1); } +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(outfile, sizeof(outfile), "%s%s", file, GZ_SUFFIX); +#else strcpy(outfile, file); strcat(outfile, GZ_SUFFIX); +#endif in = fopen(file, "rb"); if (in == NULL) { @@ -308,7 +507,11 @@ void file_uncompress(file) exit(1); } +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(buf, sizeof(buf), "%s", file); +#else strcpy(buf, file); +#endif if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) { infile = file; @@ -317,7 +520,11 @@ void file_uncompress(file) } else { outfile = file; infile = buf; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(buf + len, sizeof(buf) - len, "%s", GZ_SUFFIX); +#else strcat(infile, GZ_SUFFIX); +#endif } in = gzopen(infile, "rb"); if (in == NULL) { @@ -355,7 +562,11 @@ int main(argc, argv) gzFile file; char *bname, outmode[20]; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(outmode, sizeof(outmode), "%s", "wb6 "); +#else strcpy(outmode, "wb6 "); +#endif prog = argv[0]; bname = strrchr(argv[0], '/'); diff --git a/etc/c/zlib/osx.mak b/etc/c/zlib/osx.mak index 604f70027c6..6faafaa1836 100644 --- a/etc/c/zlib/osx.mak +++ b/etc/c/zlib/osx.mak @@ -1,95 +1,95 @@ -# Makefile for zlib - -MODEL=32 -CC=gcc -LD=link -CFLAGS=-O -m$(MODEL) -LDFLAGS= -O=.o - -.c.o: - $(CC) -c $(CFLAGS) $* - -.d.o: - $(DMD) -c $(DFLAGS) $* - -# variables -OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ - gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) - -all: zlib.a example minigzip - -adler32.o: zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -zutil.o: zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -gzclose.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzlib.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzread.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzwrite.o: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -compress.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -example.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -minigzip.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -uncompr.o: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -crc32.o: zutil.h zlib.h zconf.h crc32.h - $(CC) -c $(CFLAGS) $*.c - -deflate.o: deflate.h zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) -c $(CFLAGS) $*.c - -inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) -c $(CFLAGS) $*.c - -inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h - $(CC) -c $(CFLAGS) $*.c - -inftrees.o: zutil.h zlib.h zconf.h inftrees.h - $(CC) -c $(CFLAGS) $*.c - -trees.o: deflate.h zutil.h zlib.h zconf.h trees.h - $(CC) -c $(CFLAGS) $*.c - - -example.o: example.c zlib.h zconf.h - $(CC) -c $(cvarsdll) $(CFLAGS) $*.c - -minigzip.o: minigzip.c zlib.h zconf.h - $(CC) -c $(cvarsdll) $(CFLAGS) $*.c - -zlib.a: $(OBJS) - ar -r $@ $(OBJS) - -example: example.o zlib.a - $(CC) $(CFLAGS) -o $@ example.o zlib.a -g - -minigzip: minigzip.o zlib.a - $(CC) $(CFLAGS) -o $@ minigzip.o zlib.a -g - -test: example minigzip - ./example - echo hello world | minigzip | minigzip -d - -clean: - $(RM) $(OBJS) zlib.a example.o example minigzip minigzip.o test foo.gz - +# Makefile for zlib + +MODEL=32 +CC=gcc +LD=link +CFLAGS=-O -m$(MODEL) +LDFLAGS= +O=.o + +.c.o: + $(CC) -c $(CFLAGS) $* + +.d.o: + $(DMD) -c $(DFLAGS) $* + +# variables +OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ + gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) + +all: zlib.a example minigzip + +adler32.o: zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +zutil.o: zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +gzclose.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzlib.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzread.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzwrite.o: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +compress.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +example.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +minigzip.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +uncompr.o: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +crc32.o: zutil.h zlib.h zconf.h crc32.h + $(CC) -c $(CFLAGS) $*.c + +deflate.o: deflate.h zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) -c $(CFLAGS) $*.c + +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) -c $(CFLAGS) $*.c + +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h + $(CC) -c $(CFLAGS) $*.c + +inftrees.o: zutil.h zlib.h zconf.h inftrees.h + $(CC) -c $(CFLAGS) $*.c + +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h + $(CC) -c $(CFLAGS) $*.c + + +example.o: example.c zlib.h zconf.h + $(CC) -c $(cvarsdll) $(CFLAGS) $*.c + +minigzip.o: minigzip.c zlib.h zconf.h + $(CC) -c $(cvarsdll) $(CFLAGS) $*.c + +zlib.a: $(OBJS) + ar -r $@ $(OBJS) + +example: example.o zlib.a + $(CC) $(CFLAGS) -o $@ example.o zlib.a -g + +minigzip: minigzip.o zlib.a + $(CC) $(CFLAGS) -o $@ minigzip.o zlib.a -g + +test: example minigzip + ./example + echo hello world | minigzip | minigzip -d + +clean: + $(RM) $(OBJS) zlib.a example.o example minigzip minigzip.o test foo.gz + diff --git a/etc/c/zlib/trees.c b/etc/c/zlib/trees.c index 56e9bb1c115..1fd7759ef00 100644 --- a/etc/c/zlib/trees.c +++ b/etc/c/zlib/trees.c @@ -1,5 +1,5 @@ /* trees.c -- output deflated data using Huffman coding - * Copyright (C) 1995-2010 Jean-loup Gailly + * Copyright (C) 1995-2012 Jean-loup Gailly * detect_data_type() function provided freely by Cosmin Truta, 2006 * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -74,11 +74,6 @@ local const uch bl_order[BL_CODES] * probability, to avoid transmitting the lengths for unused bit length codes. */ -#define Buf_size (8 * 2*sizeof(char)) -/* Number of bits used within bi_buf. (bi_buf might be implemented on - * more than 16 bits on some systems.) - */ - /* =========================================================================== * Local data. These are initialized only once. */ @@ -151,8 +146,8 @@ local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); local int build_bl_tree OF((deflate_state *s)); local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, int blcodes)); -local void compress_block OF((deflate_state *s, ct_data *ltree, - ct_data *dtree)); +local void compress_block OF((deflate_state *s, const ct_data *ltree, + const ct_data *dtree)); local int detect_data_type OF((deflate_state *s)); local unsigned bi_reverse OF((unsigned value, int length)); local void bi_windup OF((deflate_state *s)); @@ -399,7 +394,6 @@ void ZLIB_INTERNAL _tr_init(s) s->bi_buf = 0; s->bi_valid = 0; - s->last_eob_len = 8; /* enough lookahead for inflate */ #ifdef DEBUG s->compressed_len = 0L; s->bits_sent = 0L; @@ -882,16 +876,18 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ } +/* =========================================================================== + * Flush the bits in the bit buffer to pending output (leaves at most 7 bits) + */ +void ZLIB_INTERNAL _tr_flush_bits(s) + deflate_state *s; +{ + bi_flush(s); +} + /* =========================================================================== * Send one empty static block to give enough lookahead for inflate. * This takes 10 bits, of which 7 may remain in the bit buffer. - * The current inflate code requires 9 bits of lookahead. If the - * last two codes for the previous block (real code plus EOB) were coded - * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode - * the last real code. In this case we send two empty static blocks instead - * of one. (There are no problems if the previous block is stored or fixed.) - * To simplify the code, we assume the worst case of last real code encoded - * on one bit only. */ void ZLIB_INTERNAL _tr_align(s) deflate_state *s; @@ -902,20 +898,6 @@ void ZLIB_INTERNAL _tr_align(s) s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ #endif bi_flush(s); - /* Of the 10 bits for the empty block, we have already sent - * (10 - bi_valid) bits. The lookahead for the last real code (before - * the EOB of the previous block) was thus at least one plus the length - * of the EOB plus what we have just sent of the empty static block. - */ - if (1 + s->last_eob_len + 10 - s->bi_valid < 9) { - send_bits(s, STATIC_TREES<<1, 3); - send_code(s, END_BLOCK, static_ltree); -#ifdef DEBUG - s->compressed_len += 10L; -#endif - bi_flush(s); - } - s->last_eob_len = 7; } /* =========================================================================== @@ -990,7 +972,8 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { #endif send_bits(s, (STATIC_TREES<<1)+last, 3); - compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree); + compress_block(s, (const ct_data *)static_ltree, + (const ct_data *)static_dtree); #ifdef DEBUG s->compressed_len += 3 + s->static_len; #endif @@ -998,7 +981,8 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) send_bits(s, (DYN_TREES<<1)+last, 3); send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, max_blindex+1); - compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree); + compress_block(s, (const ct_data *)s->dyn_ltree, + (const ct_data *)s->dyn_dtree); #ifdef DEBUG s->compressed_len += 3 + s->opt_len; #endif @@ -1075,8 +1059,8 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc) */ local void compress_block(s, ltree, dtree) deflate_state *s; - ct_data *ltree; /* literal tree */ - ct_data *dtree; /* distance tree */ + const ct_data *ltree; /* literal tree */ + const ct_data *dtree; /* distance tree */ { unsigned dist; /* distance of matched string */ int lc; /* match length or unmatched char (if dist == 0) */ @@ -1118,7 +1102,6 @@ local void compress_block(s, ltree, dtree) } while (lx < s->last_lit); send_code(s, END_BLOCK, ltree); - s->last_eob_len = ltree[END_BLOCK].Len; } /* =========================================================================== @@ -1226,7 +1209,6 @@ local void copy_block(s, buf, len, header) int header; /* true if block header must be written */ { bi_windup(s); /* align on byte boundary */ - s->last_eob_len = 8; /* enough lookahead for inflate */ if (header) { put_short(s, (ush)len); diff --git a/etc/c/zlib/uncompr.c b/etc/c/zlib/uncompr.c index ad98be3a5d8..242e9493dff 100644 --- a/etc/c/zlib/uncompr.c +++ b/etc/c/zlib/uncompr.c @@ -30,7 +30,7 @@ int ZEXPORT uncompress (dest, destLen, source, sourceLen) z_stream stream; int err; - stream.next_in = (Bytef*)source; + stream.next_in = (z_const Bytef *)source; stream.avail_in = (uInt)sourceLen; /* Check for source > 64K on 16-bit machine: */ if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; diff --git a/etc/c/zlib/win32.mak b/etc/c/zlib/win32.mak index f03a4b8f667..f6077b7dfc7 100644 --- a/etc/c/zlib/win32.mak +++ b/etc/c/zlib/win32.mak @@ -1,96 +1,96 @@ -# Makefile for zlib - -CC=dmc -LD=link -LIB=lib -CFLAGS=-o -LDFLAGS= -O=.obj - -# variables - -OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ - gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) - - -all: zlib.lib example.exe minigzip.exe - -adler32.obj: zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -zutil.obj: zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -gzclose.obj: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzlib.obj: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzread.obj: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -gzwrite.obj: zlib.h zconf.h gzguts.h - $(CC) -c $(CFLAGS) $*.c - -compress.obj: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -example.obj: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -minigzip.obj: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -uncompr.obj: zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -crc32.obj: zutil.h zlib.h zconf.h crc32.h - $(CC) -c $(CFLAGS) $*.c - -deflate.obj: deflate.h zutil.h zlib.h zconf.h - $(CC) -c $(CFLAGS) $*.c - -infback.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) -c $(CFLAGS) $*.c - -inflate.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) -c $(CFLAGS) $*.c - -inffast.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h - $(CC) -c $(CFLAGS) $*.c - -inftrees.obj: zutil.h zlib.h zconf.h inftrees.h - $(CC) -c $(CFLAGS) $*.c - -trees.obj: deflate.h zutil.h zlib.h zconf.h trees.h - $(CC) -c $(CFLAGS) $*.c - - - -example.obj: example.c zlib.h zconf.h - $(CC) -c $(cvarsdll) $(CFLAGS) $*.c - -minigzip.obj: minigzip.c zlib.h zconf.h - $(CC) -c $(cvarsdll) $(CFLAGS) $*.c - -zlib.lib: $(OBJS) - $(LIB) -c zlib.lib $(OBJS) - -example.exe: example.obj zlib.lib - $(LD) $(LDFLAGS) example.obj zlib.lib - -minigzip.exe: minigzip.obj zlib.lib - $(LD) $(LDFLAGS) minigzip.obj zlib.lib - -test: example.exe minigzip.exe - example - echo hello world | minigzip | minigzip -d - -clean: - del *.obj - del *.exe - del *.dll - del *.lib - del *.lst - del foo.gz +# Makefile for zlib + +CC=dmc +LD=link +LIB=lib +CFLAGS=-o -DNO_snprintf +LDFLAGS= +O=.obj + +# variables + +OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ + gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) + + +all: zlib.lib example.exe minigzip.exe + +adler32.obj: zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +zutil.obj: zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +gzclose.obj: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzlib.obj: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzread.obj: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +gzwrite.obj: zlib.h zconf.h gzguts.h + $(CC) -c $(CFLAGS) $*.c + +compress.obj: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +example.obj: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +minigzip.obj: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +uncompr.obj: zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +crc32.obj: zutil.h zlib.h zconf.h crc32.h + $(CC) -c $(CFLAGS) $*.c + +deflate.obj: deflate.h zutil.h zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +infback.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) -c $(CFLAGS) $*.c + +inflate.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) -c $(CFLAGS) $*.c + +inffast.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h + $(CC) -c $(CFLAGS) $*.c + +inftrees.obj: zutil.h zlib.h zconf.h inftrees.h + $(CC) -c $(CFLAGS) $*.c + +trees.obj: deflate.h zutil.h zlib.h zconf.h trees.h + $(CC) -c $(CFLAGS) $*.c + + + +example.obj: example.c zlib.h zconf.h + $(CC) -c $(cvarsdll) $(CFLAGS) $*.c + +minigzip.obj: minigzip.c zlib.h zconf.h + $(CC) -c $(cvarsdll) $(CFLAGS) $*.c + +zlib.lib: $(OBJS) + $(LIB) -c zlib.lib $(OBJS) + +example.exe: example.obj zlib.lib + $(LD) $(LDFLAGS) example.obj zlib.lib + +minigzip.exe: minigzip.obj zlib.lib + $(LD) $(LDFLAGS) minigzip.obj zlib.lib + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + del *.obj + del *.exe + del *.dll + del *.lib + del *.lst + del foo.gz diff --git a/etc/c/zlib/win64.mak b/etc/c/zlib/win64.mak index 0c387c9ade7..164c0459666 100644 --- a/etc/c/zlib/win64.mak +++ b/etc/c/zlib/win64.mak @@ -1,101 +1,101 @@ -# Makefile for zlib64 - -MODEL=64 -VCDIR=\Program Files (x86)\Microsoft Visual Studio 10.0\VC - -CC="$(VCDIR)\bin\amd64\cl" -LD="$(VCDIR)\bin\amd64\link" -LIB="$(VCDIR)\bin\amd64\lib" - -CFLAGS=/O2 /nologo /I"$(VCDIR)\INCLUDE" -LIBFLAGS=/nologo -LDFLAGS=/nologo -O=.obj - -# variables - -OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ - gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) - - -all: zlib64.lib example.exe minigzip.exe - -adler32.obj: zutil.h zlib.h zconf.h - $(CC) /c $(CFLAGS) $*.c - -zutil.obj: zutil.h zlib.h zconf.h - $(CC) /c $(CFLAGS) $*.c - -gzclose.obj: zlib.h zconf.h gzguts.h - $(CC) /c $(CFLAGS) $*.c - -gzlib.obj: zlib.h zconf.h gzguts.h - $(CC) /c $(CFLAGS) $*.c - -gzread.obj: zlib.h zconf.h gzguts.h - $(CC) /c $(CFLAGS) $*.c - -gzwrite.obj: zlib.h zconf.h gzguts.h - $(CC) /c $(CFLAGS) $*.c - -compress.obj: zlib.h zconf.h - $(CC) /c $(CFLAGS) $*.c - -example.obj: zlib.h zconf.h - $(CC) /c $(CFLAGS) $*.c - -minigzip.obj: zlib.h zconf.h - $(CC) /c $(CFLAGS) $*.c - -uncompr.obj: zlib.h zconf.h - $(CC) /c $(CFLAGS) $*.c - -crc32.obj: zutil.h zlib.h zconf.h crc32.h - $(CC) /c $(CFLAGS) $*.c - -deflate.obj: deflate.h zutil.h zlib.h zconf.h - $(CC) /c $(CFLAGS) $*.c - -infback.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) /c $(CFLAGS) $*.c - -inflate.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h - $(CC) /c $(CFLAGS) $*.c - -inffast.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h - $(CC) /c $(CFLAGS) $*.c - -inftrees.obj: zutil.h zlib.h zconf.h inftrees.h - $(CC) /c $(CFLAGS) $*.c - -trees.obj: deflate.h zutil.h zlib.h zconf.h trees.h - $(CC) /c $(CFLAGS) $*.c - - - -example.obj: example.c zlib.h zconf.h - $(CC) /c $(cvarsdll) $(CFLAGS) $*.c - -minigzip.obj: minigzip.c zlib.h zconf.h - $(CC) /c $(cvarsdll) $(CFLAGS) $*.c - -zlib$(MODEL).lib: $(OBJS) - $(LIB) $(LIBFLAGS) /OUT:zlib$(MODEL).lib $(OBJS) - -example.exe: example.obj zlib$(MODEL).lib - $(LD) $(LDFLAGS) example.obj zlib$(MODEL).lib - -minigzip.exe: minigzip.obj zlib$(MODEL).lib - $(LD) $(LDFLAGS) minigzip.obj zlib$(MODEL).lib - -test: example.exe minigzip.exe - example - echo hello world | minigzip | minigzip -d - -clean: - del *.obj - del *.exe - del *.dll - del *.lib - del *.lst - del foo.gz +# Makefile for zlib64 + +MODEL=64 +VCDIR=\Program Files (x86)\Microsoft Visual Studio 10.0\VC + +CC="$(VCDIR)\bin\amd64\cl" +LD="$(VCDIR)\bin\amd64\link" +LIB="$(VCDIR)\bin\amd64\lib" + +CFLAGS=/O2 /nologo /I"$(VCDIR)\INCLUDE" +LIBFLAGS=/nologo +LDFLAGS=/nologo +O=.obj + +# variables + +OBJS = adler32$(O) compress$(O) crc32$(O) deflate$(O) gzclose$(O) gzlib$(O) gzread$(O) \ + gzwrite$(O) infback$(O) inffast$(O) inflate$(O) inftrees$(O) trees$(O) uncompr$(O) zutil$(O) + + +all: zlib64.lib example.exe minigzip.exe + +adler32.obj: zutil.h zlib.h zconf.h + $(CC) /c $(CFLAGS) $*.c + +zutil.obj: zutil.h zlib.h zconf.h + $(CC) /c $(CFLAGS) $*.c + +gzclose.obj: zlib.h zconf.h gzguts.h + $(CC) /c $(CFLAGS) $*.c + +gzlib.obj: zlib.h zconf.h gzguts.h + $(CC) /c $(CFLAGS) $*.c + +gzread.obj: zlib.h zconf.h gzguts.h + $(CC) /c $(CFLAGS) $*.c + +gzwrite.obj: zlib.h zconf.h gzguts.h + $(CC) /c $(CFLAGS) $*.c + +compress.obj: zlib.h zconf.h + $(CC) /c $(CFLAGS) $*.c + +example.obj: zlib.h zconf.h + $(CC) /c $(CFLAGS) $*.c + +minigzip.obj: zlib.h zconf.h + $(CC) /c $(CFLAGS) $*.c + +uncompr.obj: zlib.h zconf.h + $(CC) /c $(CFLAGS) $*.c + +crc32.obj: zutil.h zlib.h zconf.h crc32.h + $(CC) /c $(CFLAGS) $*.c + +deflate.obj: deflate.h zutil.h zlib.h zconf.h + $(CC) /c $(CFLAGS) $*.c + +infback.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) /c $(CFLAGS) $*.c + +inflate.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h + $(CC) /c $(CFLAGS) $*.c + +inffast.obj: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h + $(CC) /c $(CFLAGS) $*.c + +inftrees.obj: zutil.h zlib.h zconf.h inftrees.h + $(CC) /c $(CFLAGS) $*.c + +trees.obj: deflate.h zutil.h zlib.h zconf.h trees.h + $(CC) /c $(CFLAGS) $*.c + + + +example.obj: example.c zlib.h zconf.h + $(CC) /c $(cvarsdll) $(CFLAGS) $*.c + +minigzip.obj: minigzip.c zlib.h zconf.h + $(CC) /c $(cvarsdll) $(CFLAGS) $*.c + +zlib$(MODEL).lib: $(OBJS) + $(LIB) $(LIBFLAGS) /OUT:zlib$(MODEL).lib $(OBJS) + +example.exe: example.obj zlib$(MODEL).lib + $(LD) $(LDFLAGS) example.obj zlib$(MODEL).lib + +minigzip.exe: minigzip.obj zlib$(MODEL).lib + $(LD) $(LDFLAGS) minigzip.obj zlib$(MODEL).lib + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + del *.obj + del *.exe + del *.dll + del *.lib + del *.lst + del foo.gz diff --git a/etc/c/zlib/zconf.h b/etc/c/zlib/zconf.h index 02ce56c4313..9987a775530 100644 --- a/etc/c/zlib/zconf.h +++ b/etc/c/zlib/zconf.h @@ -1,5 +1,5 @@ /* zconf.h -- configuration of the zlib compression library - * Copyright (C) 1995-2010 Jean-loup Gailly. + * Copyright (C) 1995-2013 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -15,11 +15,13 @@ * this permanently in zconf.h using "./configure --zprefix". */ #ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET /* all linked symbols */ # define _dist_code z__dist_code # define _length_code z__length_code # define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits # define _tr_flush_block z__tr_flush_block # define _tr_init z__tr_init # define _tr_stored_block z__tr_stored_block @@ -27,9 +29,11 @@ # define adler32 z_adler32 # define adler32_combine z_adler32_combine # define adler32_combine64 z_adler32_combine64 -# define compress z_compress -# define compress2 z_compress2 -# define compressBound z_compressBound +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif # define crc32 z_crc32 # define crc32_combine z_crc32_combine # define crc32_combine64 z_crc32_combine64 @@ -40,44 +44,53 @@ # define deflateInit2_ z_deflateInit2_ # define deflateInit_ z_deflateInit_ # define deflateParams z_deflateParams +# define deflatePending z_deflatePending # define deflatePrime z_deflatePrime # define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep # define deflateSetDictionary z_deflateSetDictionary # define deflateSetHeader z_deflateSetHeader # define deflateTune z_deflateTune # define deflate_copyright z_deflate_copyright # define get_crc_table z_get_crc_table -# define gz_error z_gz_error -# define gz_intmax z_gz_intmax -# define gz_strwinerror z_gz_strwinerror -# define gzbuffer z_gzbuffer -# define gzclearerr z_gzclearerr -# define gzclose z_gzclose -# define gzclose_r z_gzclose_r -# define gzclose_w z_gzclose_w -# define gzdirect z_gzdirect -# define gzdopen z_gzdopen -# define gzeof z_gzeof -# define gzerror z_gzerror -# define gzflush z_gzflush -# define gzgetc z_gzgetc -# define gzgets z_gzgets -# define gzoffset z_gzoffset -# define gzoffset64 z_gzoffset64 -# define gzopen z_gzopen -# define gzopen64 z_gzopen64 -# define gzprintf z_gzprintf -# define gzputc z_gzputc -# define gzputs z_gzputs -# define gzread z_gzread -# define gzrewind z_gzrewind -# define gzseek z_gzseek -# define gzseek64 z_gzseek64 -# define gzsetparams z_gzsetparams -# define gztell z_gztell -# define gztell64 z_gztell64 -# define gzungetc z_gzungetc -# define gzwrite z_gzwrite +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzvprintf z_gzvprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzwrite z_gzwrite +# endif # define inflate z_inflate # define inflateBack z_inflateBack # define inflateBackEnd z_inflateBackEnd @@ -92,16 +105,22 @@ # define inflateReset z_inflateReset # define inflateReset2 z_inflateReset2 # define inflateSetDictionary z_inflateSetDictionary +# define inflateGetDictionary z_inflateGetDictionary # define inflateSync z_inflateSync # define inflateSyncPoint z_inflateSyncPoint # define inflateUndermine z_inflateUndermine +# define inflateResetKeep z_inflateResetKeep # define inflate_copyright z_inflate_copyright # define inflate_fast z_inflate_fast # define inflate_table z_inflate_table -# define uncompress z_uncompress +# ifndef Z_SOLO +# define uncompress z_uncompress +# endif # define zError z_zError -# define zcalloc z_zcalloc -# define zcfree z_zcfree +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif # define zlibCompileFlags z_zlibCompileFlags # define zlibVersion z_zlibVersion @@ -111,7 +130,9 @@ # define alloc_func z_alloc_func # define charf z_charf # define free_func z_free_func -# define gzFile z_gzFile +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif # define gz_header z_gz_header # define gz_headerp z_gz_headerp # define in_func z_in_func @@ -197,6 +218,12 @@ # endif #endif +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + /* Some Mac compilers merge all .h files incorrectly: */ #if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) # define NO_DUMMY_DECL @@ -243,6 +270,14 @@ # endif #endif +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + /* The following definitions for FAR are needed only for MSDOS mixed * model programming (small or medium model with some far allocations). * This was tested only with MSC; for other MSDOS compilers you may have @@ -356,12 +391,47 @@ typedef uLong FAR uLongf; typedef Byte *voidp; #endif +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + #ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ # define Z_HAVE_UNISTD_H #endif +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + #ifdef STDC -# include /* for off_t */ +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif #endif /* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and @@ -370,21 +440,38 @@ typedef uLong FAR uLongf; * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as * equivalently requesting no 64-bit operations */ -#if -_LARGEFILE64_SOURCE - -1 == 1 +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 # undef _LARGEFILE64_SOURCE #endif -#if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) -# include /* for SEEK_* and off_t */ -# ifdef VMS -# include /* for off_t */ -# endif -# ifndef z_off_t -# define z_off_t off_t +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif # endif #endif -#ifndef SEEK_SET +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) # define SEEK_SET 0 /* Seek from beginning of file. */ # define SEEK_CUR 1 /* Seek from current position. */ # define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ @@ -394,18 +481,14 @@ typedef uLong FAR uLongf; # define z_off_t long #endif -#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 +#if !defined(_WIN32) && defined(Z_LARGE64) # define z_off64_t off64_t #else -# define z_off64_t z_off_t -#endif - -#if defined(__OS400__) -# define NO_vsnprintf -#endif - -#if defined(__MVS__) -# define NO_vsnprintf +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif #endif /* MVS linker does not support external names larger than 8 bytes */ diff --git a/etc/c/zlib/zlib.3 b/etc/c/zlib/zlib.3 index 27adc4cd10a..0160e62b69f 100644 --- a/etc/c/zlib/zlib.3 +++ b/etc/c/zlib/zlib.3 @@ -1,4 +1,4 @@ -.TH ZLIB 3 "19 Apr 2010" +.TH ZLIB 3 "28 Apr 2013" .SH NAME zlib \- compression/decompression library .SH SYNOPSIS @@ -36,9 +36,9 @@ All functions of the compression library are documented in the file .IR zlib.h . The distribution source includes examples of use of the library in the files -.I example.c +.I test/example.c and -.IR minigzip.c, +.IR test/minigzip.c, as well as other examples in the .IR examples/ directory. @@ -65,7 +65,7 @@ A Python interface to written by A.M. Kuchling (amk@magnet.com), is available in Python 1.5 and later versions: .IP -http://www.python.org/doc/lib/module-zlib.html +http://docs.python.org/library/zlib.html .LP .I zlib is built into @@ -95,11 +95,11 @@ http://zlib.net/ The data format used by the zlib library is described by RFC (Request for Comments) 1950 to 1952 in the files: .IP -http://www.ietf.org/rfc/rfc1950.txt (for the zlib header and trailer format) +http://tools.ietf.org/html/rfc1950 (for the zlib header and trailer format) .br -http://www.ietf.org/rfc/rfc1951.txt (for the deflate compressed data format) +http://tools.ietf.org/html/rfc1951 (for the deflate compressed data format) .br -http://www.ietf.org/rfc/rfc1952.txt (for the gzip header and trailer format) +http://tools.ietf.org/html/rfc1952 (for the gzip header and trailer format) .LP Mark Nelson wrote an article about .I zlib @@ -125,8 +125,8 @@ before asking for help. Send questions and/or comments to zlib@gzip.org, or (for the Windows DLL version) to Gilles Vollant (info@winimage.com). .SH AUTHORS -Version 1.2.5 -Copyright (C) 1995-2010 Jean-loup Gailly (jloup@gzip.org) +Version 1.2.8 +Copyright (C) 1995-2013 Jean-loup Gailly (jloup@gzip.org) and Mark Adler (madler@alumni.caltech.edu). .LP This software is provided "as-is," diff --git a/etc/c/zlib/zlib.h b/etc/c/zlib/zlib.h index bfbba83e8ee..3e0c7672ac5 100644 --- a/etc/c/zlib/zlib.h +++ b/etc/c/zlib/zlib.h @@ -1,7 +1,7 @@ /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.5, April 19th, 2010 + version 1.2.8, April 28th, 2013 - Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,8 +24,8 @@ The data format used by the zlib library is described by RFCs (Request for - Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt - (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). + Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 + (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ #ifndef ZLIB_H @@ -37,11 +37,11 @@ extern "C" { #endif -#define ZLIB_VERSION "1.2.5" -#define ZLIB_VERNUM 0x1250 +#define ZLIB_VERSION "1.2.8" +#define ZLIB_VERNUM 0x1280 #define ZLIB_VER_MAJOR 1 #define ZLIB_VER_MINOR 2 -#define ZLIB_VER_REVISION 5 +#define ZLIB_VER_REVISION 8 #define ZLIB_VER_SUBREVISION 0 /* @@ -83,15 +83,15 @@ typedef void (*free_func) OF((voidpf opaque, voidpf address)); struct internal_state; typedef struct z_stream_s { - Bytef *next_in; /* next input byte */ + z_const Bytef *next_in; /* next input byte */ uInt avail_in; /* number of bytes available at next_in */ - uLong total_in; /* total nb of input bytes read so far */ + uLong total_in; /* total number of input bytes read so far */ Bytef *next_out; /* next output byte should be put there */ uInt avail_out; /* remaining free space at next_out */ - uLong total_out; /* total nb of bytes output so far */ + uLong total_out; /* total number of bytes output so far */ - char *msg; /* last error message, NULL if no error */ + z_const char *msg; /* last error message, NULL if no error */ struct internal_state FAR *state; /* not visible by applications */ alloc_func zalloc; /* used to allocate the internal state */ @@ -327,8 +327,9 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); Z_FINISH can be used immediately after deflateInit if all the compression is to be done in a single step. In this case, avail_out must be at least the - value returned by deflateBound (see below). If deflate does not return - Z_STREAM_END, then it must be called again as described above. + value returned by deflateBound (see below). Then deflate is guaranteed to + return Z_STREAM_END. If not enough output space is provided, deflate will + not return Z_STREAM_END, and it must be called again as described above. deflate() sets strm->adler to the adler32 checksum of all input read so far (that is, total_in bytes). @@ -451,23 +452,29 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); error. However if all decompression is to be performed in a single step (a single call of inflate), the parameter flush should be set to Z_FINISH. In this case all pending input is processed and all pending output is flushed; - avail_out must be large enough to hold all the uncompressed data. (The size - of the uncompressed data may have been saved by the compressor for this - purpose.) The next operation on this stream must be inflateEnd to deallocate - the decompression state. The use of Z_FINISH is never required, but can be - used to inform inflate that a faster approach may be used for the single - inflate() call. + avail_out must be large enough to hold all of the uncompressed data for the + operation to complete. (The size of the uncompressed data may have been + saved by the compressor for this purpose.) The use of Z_FINISH is not + required to perform an inflation in one step. However it may be used to + inform inflate that a faster approach can be used for the single inflate() + call. Z_FINISH also informs inflate to not maintain a sliding window if the + stream completes, which reduces inflate's memory footprint. If the stream + does not complete, either because not all of the stream is provided or not + enough output space is provided, then a sliding window will be allocated and + inflate() can be called again to continue the operation as if Z_NO_FLUSH had + been used. In this implementation, inflate() always flushes as much output as possible to the output buffer, and always uses the faster approach on the - first call. So the only effect of the flush parameter in this implementation - is on the return value of inflate(), as noted below, or when it returns early - because Z_BLOCK or Z_TREES is used. + first call. So the effects of the flush parameter in this implementation are + on the return value of inflate() as noted below, when inflate() returns early + when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of + memory for a sliding window when Z_FINISH is used. If a preset dictionary is needed after this call (see inflateSetDictionary - below), inflate sets strm->adler to the adler32 checksum of the dictionary + below), inflate sets strm->adler to the Adler-32 checksum of the dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise it sets - strm->adler to the adler32 checksum of all output produced so far (that is, + strm->adler to the Adler-32 checksum of all output produced so far (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described below. At the end of the stream, inflate() checks that its computed adler32 checksum is equal to that saved by the compressor and returns Z_STREAM_END @@ -478,7 +485,9 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); initializing with inflateInit2(). Any information contained in the gzip header is not retained, so applications that need that information should instead use raw inflate, see inflateInit2() below, or inflateBack() and - perform their own processing of the gzip header and trailer. + perform their own processing of the gzip header and trailer. When processing + gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output + producted so far. The CRC-32 is checked against the gzip trailer. inflate() returns Z_OK if some progress has been made (more input processed or more output produced), Z_STREAM_END if the end of the compressed data has @@ -580,10 +589,15 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, uInt dictLength)); /* Initializes the compression dictionary from the given byte sequence - without producing any compressed output. This function must be called - immediately after deflateInit, deflateInit2 or deflateReset, before any call - of deflate. The compressor and decompressor must use exactly the same - dictionary (see inflateSetDictionary). + without producing any compressed output. When using the zlib format, this + function must be called immediately after deflateInit, deflateInit2 or + deflateReset, and before any call of deflate. When doing raw deflate, this + function must be called either before any call of deflate, or immediately + after the completion of a deflate block, i.e. after all input has been + consumed and all output has been delivered when using any of the flush + options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The + compressor and decompressor must use exactly the same dictionary (see + inflateSetDictionary). The dictionary should consist of strings (byte sequences) that are likely to be encountered later in the data to be compressed, with the most commonly @@ -610,8 +624,8 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is inconsistent (for example if deflate has already been called for this stream - or if the compression method is bsort). deflateSetDictionary does not - perform any compression: this will be done by deflate(). + or if not at a block boundary for raw deflate). deflateSetDictionary does + not perform any compression: this will be done by deflate(). */ ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, @@ -688,8 +702,28 @@ ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, deflation of sourceLen bytes. It must be called after deflateInit() or deflateInit2(), and after deflateSetHeader(), if used. This would be used to allocate an output buffer for deflation in a single pass, and so would be - called before deflate(). -*/ + called before deflate(). If that first deflate() call is provided the + sourceLen input bytes, an output buffer allocated to the size returned by + deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed + to return Z_STREAM_END. Note that it is possible for the compressed size to + be larger than the value returned by deflateBound() if flush options other + than Z_FINISH or Z_NO_FLUSH are used. +*/ + +ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, + unsigned *pending, + int *bits)); +/* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not + provided would be due to the available output space having being consumed. + The number of bits of output not provided are between 0 and 7, where they + await more bits to join them in order to fill out a full byte. If pending + or bits are Z_NULL, then those values are not set. + + deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. + */ ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, int bits, @@ -703,8 +737,9 @@ ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, than or equal to 16, and that many of the least significant bits of value will be inserted in the output. - deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source - stream state was inconsistent. + deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough + room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the + source stream state was inconsistent. */ ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, @@ -790,10 +825,11 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, if that call returned Z_NEED_DICT. The dictionary chosen by the compressor can be determined from the adler32 value returned by that call of inflate. The compressor and decompressor must use exactly the same dictionary (see - deflateSetDictionary). For raw inflate, this function can be called - immediately after inflateInit2() or inflateReset() and before any call of - inflate() to set the dictionary. The application must insure that the - dictionary that was used for compression is provided. + deflateSetDictionary). For raw inflate, this function can be called at any + time to set the dictionary. If the provided dictionary is smaller than the + window and there is already data in the window, then the provided dictionary + will amend what's there. The application must insure that the dictionary + that was used for compression is provided. inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is @@ -803,19 +839,38 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, inflate(). */ +ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, + uInt *dictLength)); +/* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If inflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); /* - Skips invalid compressed data until a full flush point (see above the - description of deflate with Z_FULL_FLUSH) can be found, or until all + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all available input is skipped. No output is provided. - inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR - if no more input was provided, Z_DATA_ERROR if no flush point has been - found, or Z_STREAM_ERROR if the stream structure was inconsistent. In the - success case, the application may save the current current value of total_in - which indicates where valid compressed data was found. In the error case, - the application may repeatedly call inflateSync, providing more input each - time, until success or end of the input data. + inflateSync searches for a 00 00 FF FF pattern in the compressed data. + All full flush points have this pattern, but not all occurrences of this + pattern are full flush points. + + inflateSync returns Z_OK if a possible full flush point has been found, + Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point + has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. + In the success case, the application may save the current current value of + total_in which indicates where valid compressed data was found. In the + error case, the application may repeatedly call inflateSync, providing more + input each time, until success or end of the input data. */ ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, @@ -962,12 +1017,13 @@ ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, See inflateBack() for the usage of these routines. inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of - the paramaters are invalid, Z_MEM_ERROR if the internal state could not be + the parameters are invalid, Z_MEM_ERROR if the internal state could not be allocated, or Z_VERSION_ERROR if the version of the library does not match the version of the header file. */ -typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef unsigned (*in_func) OF((void FAR *, + z_const unsigned char FAR * FAR *)); typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, @@ -975,11 +1031,12 @@ ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, out_func out, void FAR *out_desc)); /* inflateBack() does a raw inflate with a single call using a call-back - interface for input and output. This is more efficient than inflate() for - file i/o applications in that it avoids copying between the output and the - sliding window by simply making the window itself the output buffer. This - function trusts the application to not change the output buffer passed by - the output function, at least until inflateBack() returns. + interface for input and output. This is potentially more efficient than + inflate() for file i/o applications, in that it avoids copying between the + output and the sliding window by simply making the window itself the output + buffer. inflate() can be faster on modern CPUs when used with large + buffers. inflateBack() trusts the application to not change the output + buffer passed by the output function, at least until inflateBack() returns. inflateBackInit() must be called first to allocate the internal state and to initialize the state with the user-provided window buffer. @@ -1088,6 +1145,7 @@ ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); 27-31: 0 (reserved) */ +#ifndef Z_SOLO /* utility functions */ @@ -1149,10 +1207,11 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if there was not enough room in the output - buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In + the case where there is not enough room, uncompress() will fill the output + buffer with the uncompressed data up to that point. */ - /* gzip file access functions */ /* @@ -1162,7 +1221,7 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, wrapper, documented in RFC 1952, wrapped around a deflate stream. */ -typedef voidp gzFile; /* opaque gzip file descriptor */ +typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ /* ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); @@ -1172,13 +1231,28 @@ ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F' for fixed code compression as in "wb9F". (See the description of - deflateInit2 for more information about the strategy parameter.) Also "a" - can be used instead of "w" to request that the gzip stream that will be - written be appended to the file. "+" will result in an error, since reading - and writing to the same gzip file is not supported. + deflateInit2 for more information about the strategy parameter.) 'T' will + request transparent writing or appending with no compression and not using + the gzip format. + + "a" can be used instead of "w" to request that the gzip stream that will + be written be appended to the file. "+" will result in an error, since + reading and writing to the same gzip file is not supported. The addition of + "x" when writing will create the file exclusively, which fails if the file + already exists. On systems that support it, the addition of "e" when + reading or writing will set the flag to close the file on an execve() call. + + These functions, as well as gzip, will read and decode a sequence of gzip + streams in a file. The append function of gzopen() can be used to create + such a file. (Also see gzflush() for another way to do this.) When + appending, gzopen does not test whether the file begins with a gzip stream, + nor does it look for the end of the gzip streams to begin appending. gzopen + will simply append a gzip stream to the existing file. gzopen can be used to read a file which is not in gzip format; in this - case gzread will directly read from the file without decompression. + case gzread will directly read from the file without decompression. When + reading, this will be detected automatically by looking for the magic two- + byte gzip header. gzopen returns NULL if the file could not be opened, if there was insufficient memory to allocate the gzFile state, or if an invalid mode was @@ -1197,7 +1271,11 @@ ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd, mode);. The duplicated descriptor should be saved to avoid a leak, since - gzdopen does not close fd if it fails. + gzdopen does not close fd if it fails. If you are using fileno() to get the + file descriptor from a FILE *, then you will have to use dup() to avoid + double-close()ing the file descriptor. Both gzclose() and fclose() will + close the associated file descriptor, so they need to have different file + descriptors. gzdopen returns NULL if there was insufficient memory to allocate the gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not @@ -1235,14 +1313,26 @@ ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); /* Reads the given number of uncompressed bytes from the compressed file. If - the input file was not in gzip format, gzread copies the given number of - bytes into the buffer. + the input file is not in gzip format, gzread copies the given number of + bytes into the buffer directly from the file. After reaching the end of a gzip stream in the input, gzread will continue - to read, looking for another gzip stream, or failing that, reading the rest - of the input file directly without decompression. The entire input file - will be read if gzread is called until it returns less than the requested - len. + to read, looking for another gzip stream. Any number of gzip streams may be + concatenated in the input file, and will all be decompressed by gzread(). + If something other than a gzip stream is encountered after a gzip stream, + that remaining trailing garbage is ignored (and no error is returned). + + gzread can be used to read a gzip file that is being concurrently written. + Upon reaching the end of the input, gzread will return with the available + data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then + gzclearerr can be used to clear the end of file indicator in order to permit + gzread to be tried again. Z_OK indicates that a gzip stream was completed + on the last gzread. Z_BUF_ERROR indicates that the input file ended in the + middle of a gzip stream. Note that gzread does not return -1 in the event + of an incomplete gzip stream. This error is deferred until gzclose(), which + will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip + stream. Alternatively, gzerror can be used before gzclose to detect this + case. gzread returns the number of uncompressed bytes actually read, less than len for end of file, or -1 for error. @@ -1256,7 +1346,7 @@ ZEXTERN int ZEXPORT gzwrite OF((gzFile file, error. */ -ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); /* Converts, formats, and writes the arguments to the compressed file under control of the format string, as in fprintf. gzprintf returns the number of @@ -1301,7 +1391,10 @@ ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); /* Reads one byte from the compressed file. gzgetc returns this byte or -1 - in case of end of file or error. + in case of end of file or error. This is implemented as a macro for speed. + As such, it does not do all of the checking the other functions do. I.e. + it does not check to see if file is NULL, nor whether the structure file + points to has been clobbered or not. */ ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); @@ -1397,9 +1490,7 @@ ZEXTERN int ZEXPORT gzeof OF((gzFile file)); ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); /* Returns true (1) if file is being copied directly while reading, or false - (0) if file is a gzip stream being decompressed. This state can change from - false to true while reading the input file if the end of a gzip stream is - reached, but is followed by data that is not another gzip stream. + (0) if file is a gzip stream being decompressed. If the input file is empty, gzdirect() will return true, since the input does not contain a gzip stream. @@ -1408,6 +1499,13 @@ ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); cause buffers to be allocated to allow reading the file to determine if it is a gzip file. Therefore if gzbuffer() is used, it should be called before gzdirect(). + + When writing, gzdirect() returns true (1) if transparent writing was + requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note: + gzdirect() is not needed when writing. Transparent writing must be + explicitly requested, so the application already knows the answer. When + linking statically, using gzdirect() will include all of the zlib code for + gzip file reading and decompression, which may not be desired.) */ ZEXTERN int ZEXPORT gzclose OF((gzFile file)); @@ -1419,7 +1517,8 @@ ZEXTERN int ZEXPORT gzclose OF((gzFile file)); must not be called more than once on the same allocation. gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a - file operation error, or Z_OK on success. + file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the + last read ended in the middle of a gzip stream, or Z_OK on success. */ ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); @@ -1457,6 +1556,7 @@ ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); file that is being written concurrently. */ +#endif /* !Z_SOLO */ /* checksum functions */ @@ -1492,16 +1592,17 @@ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of - seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note + that the z_off_t type (like off_t) is a signed integer. If len2 is + negative, the result has no meaning or utility. */ ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); /* Update a running CRC-32 with the bytes buf[0..len-1] and return the updated CRC-32. If buf is Z_NULL, this function returns the required - initial value for the for the crc. Pre- and post-conditioning (one's - complement) is performed within this function so it shouldn't be done by the - application. + initial value for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. Usage example: @@ -1544,17 +1645,42 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, const char *version, int stream_size)); #define deflateInit(strm, level) \ - deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) #define inflateInit(strm) \ - inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) + inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream)) #define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ - (strategy), ZLIB_VERSION, sizeof(z_stream)) + (strategy), ZLIB_VERSION, (int)sizeof(z_stream)) #define inflateInit2(strm, windowBits) \ - inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + inflateInit2_((strm), (windowBits), ZLIB_VERSION, \ + (int)sizeof(z_stream)) #define inflateBackInit(strm, windowBits, window) \ inflateBackInit_((strm), (windowBits), (window), \ - ZLIB_VERSION, sizeof(z_stream)) + ZLIB_VERSION, (int)sizeof(z_stream)) + +#ifndef Z_SOLO + +/* gzgetc() macro and its supporting function and exposed data structure. Note + * that the real internal state is much larger than the exposed structure. + * This abbreviated structure exposes just enough for the gzgetc() macro. The + * user should not mess with these exposed elements, since their names or + * behavior could change in the future, perhaps even capriciously. They can + * only be used by the gzgetc() macro. You have been warned. + */ +struct gzFile_s { + unsigned have; + unsigned char *next; + z_off64_t pos; +}; +ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +# define z_gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) +#else +# define gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) +#endif /* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if @@ -1562,7 +1688,7 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, * functions are changed to 64 bits) -- in case these are set on systems * without large file support, _LFS64_LARGEFILE must also be true */ -#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 +#ifdef Z_LARGE64 ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); @@ -1571,14 +1697,23 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); #endif -#if !defined(ZLIB_INTERNAL) && _FILE_OFFSET_BITS-0 == 64 && _LFS64_LARGEFILE-0 -# define gzopen gzopen64 -# define gzseek gzseek64 -# define gztell gztell64 -# define gzoffset gzoffset64 -# define adler32_combine adler32_combine64 -# define crc32_combine crc32_combine64 -# ifdef _LARGEFILE64_SOURCE +#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) +# ifdef Z_PREFIX_SET +# define z_gzopen z_gzopen64 +# define z_gzseek z_gzseek64 +# define z_gztell z_gztell64 +# define z_gzoffset z_gzoffset64 +# define z_adler32_combine z_adler32_combine64 +# define z_crc32_combine z_crc32_combine64 +# else +# define gzopen gzopen64 +# define gzseek gzseek64 +# define gztell gztell64 +# define gzoffset gzoffset64 +# define adler32_combine adler32_combine64 +# define crc32_combine crc32_combine64 +# endif +# ifndef Z_LARGE64 ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); @@ -1595,6 +1730,13 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); #endif +#else /* Z_SOLO */ + + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + +#endif /* !Z_SOLO */ + /* hack for buggy compilers */ #if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) struct internal_state {int dummy;}; @@ -1603,8 +1745,21 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, /* undocumented functions */ ZEXTERN const char * ZEXPORT zError OF((int)); ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); -ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); +ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); +ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); +ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); +#if defined(_WIN32) && !defined(Z_SOLO) +ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, + const char *mode)); +#endif +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, + const char *format, + va_list va)); +# endif +#endif #ifdef __cplusplus } diff --git a/etc/c/zlib/zutil.c b/etc/c/zlib/zutil.c index 898ed345b0e..23d2ebef008 100644 --- a/etc/c/zlib/zutil.c +++ b/etc/c/zlib/zutil.c @@ -1,17 +1,20 @@ /* zutil.c -- target dependent utility functions for the compression library - * Copyright (C) 1995-2005, 2010 Jean-loup Gailly. + * Copyright (C) 1995-2005, 2010, 2011, 2012 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ /* @(#) $Id$ */ #include "zutil.h" +#ifndef Z_SOLO +# include "gzguts.h" +#endif #ifndef NO_DUMMY_DECL struct internal_state {int dummy;}; /* for buggy compilers */ #endif -const char * const z_errmsg[10] = { +z_const char * const z_errmsg[10] = { "need dictionary", /* Z_NEED_DICT 2 */ "stream end", /* Z_STREAM_END 1 */ "", /* Z_OK 0 */ @@ -85,27 +88,27 @@ uLong ZEXPORT zlibCompileFlags() #ifdef FASTEST flags += 1L << 21; #endif -#ifdef STDC +#if defined(STDC) || defined(Z_HAVE_STDARG_H) # ifdef NO_vsnprintf - flags += 1L << 25; + flags += 1L << 25; # ifdef HAS_vsprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # else # ifdef HAS_vsnprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # endif #else - flags += 1L << 24; + flags += 1L << 24; # ifdef NO_snprintf - flags += 1L << 25; + flags += 1L << 25; # ifdef HAS_sprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # else # ifdef HAS_snprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # endif #endif @@ -181,6 +184,7 @@ void ZLIB_INTERNAL zmemzero(dest, len) } #endif +#ifndef Z_SOLO #ifdef SYS16BIT @@ -316,3 +320,5 @@ void ZLIB_INTERNAL zcfree (opaque, ptr) } #endif /* MY_ZCALLOC */ + +#endif /* !Z_SOLO */ diff --git a/etc/c/zlib/zutil.h b/etc/c/zlib/zutil.h index 258fa88799a..24ab06b1cf6 100644 --- a/etc/c/zlib/zutil.h +++ b/etc/c/zlib/zutil.h @@ -1,5 +1,5 @@ /* zutil.h -- internal interface and configuration of the compression library - * Copyright (C) 1995-2010 Jean-loup Gailly. + * Copyright (C) 1995-2013 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -13,7 +13,7 @@ #ifndef ZUTIL_H #define ZUTIL_H -#if ((__GNUC__-0) * 10 + __GNUC_MINOR__-0 >= 33) && !defined(NO_VIZ) +#ifdef HAVE_HIDDEN # define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) #else # define ZLIB_INTERNAL @@ -21,7 +21,7 @@ #include "zlib.h" -#ifdef STDC +#if defined(STDC) && !defined(Z_SOLO) # if !(defined(_WIN32_WCE) && defined(_MSC_VER)) # include # endif @@ -29,6 +29,10 @@ # include #endif +#ifdef Z_SOLO + typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */ +#endif + #ifndef local # define local static #endif @@ -40,13 +44,13 @@ typedef unsigned short ush; typedef ush FAR ushf; typedef unsigned long ulg; -extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* (size given to avoid silly warnings with Visual C++) */ #define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] #define ERR_RETURN(strm,err) \ - return (strm->msg = (char*)ERR_MSG(err), (err)) + return (strm->msg = ERR_MSG(err), (err)) /* To be used only when the state is known to be valid */ /* common constants */ @@ -78,16 +82,18 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) # define OS_CODE 0x00 -# if defined(__TURBOC__) || defined(__BORLANDC__) -# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) - /* Allow compilation with ANSI keywords only enabled */ - void _Cdecl farfree( void *block ); - void *_Cdecl farmalloc( unsigned long nbytes ); -# else -# include +# ifndef Z_SOLO +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include # endif -# else /* MSC or DJGPP */ -# include # endif #endif @@ -107,18 +113,20 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #ifdef OS2 # define OS_CODE 0x06 -# ifdef M_I86 +# if defined(M_I86) && !defined(Z_SOLO) # include # endif #endif #if defined(MACOS) || defined(TARGET_OS_MAC) # define OS_CODE 0x07 -# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os -# include /* for fdopen */ -# else -# ifndef fdopen -# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef Z_SOLO +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif # endif # endif #endif @@ -153,14 +161,15 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # endif #endif -#if defined(__BORLANDC__) +#if defined(__BORLANDC__) && !defined(MSDOS) #pragma warn -8004 #pragma warn -8008 #pragma warn -8066 #endif /* provide prototypes for these when building zlib without LFS */ -#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 +#if !defined(_WIN32) && \ + (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); #endif @@ -177,42 +186,7 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* functions */ -#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) -# ifndef HAVE_VSNPRINTF -# define HAVE_VSNPRINTF -# endif -#endif -#if defined(__CYGWIN__) -# ifndef HAVE_VSNPRINTF -# define HAVE_VSNPRINTF -# endif -#endif -#ifndef HAVE_VSNPRINTF -# ifdef MSDOS - /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), - but for now we just assume it doesn't. */ -# define NO_vsnprintf -# endif -# ifdef __TURBOC__ -# define NO_vsnprintf -# endif -# ifdef WIN32 - /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ -# if !defined(vsnprintf) && !defined(NO_vsnprintf) -# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) -# define vsnprintf _vsnprintf -# endif -# endif -# endif -# ifdef __SASC -# define NO_vsnprintf -# endif -#endif -#ifdef VMS -# define NO_vsnprintf -#endif - -#if defined(pyr) +#if defined(pyr) || defined(Z_SOLO) # define NO_MEMCPY #endif #if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) @@ -261,14 +235,19 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # define Tracecv(c,x) #endif - -voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, - unsigned size)); -void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); +#ifndef Z_SOLO + voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, + unsigned size)); + void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); +#endif #define ZALLOC(strm, items, size) \ (*((strm)->zalloc))((strm)->opaque, (items), (size)) #define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) #define TRY_FREE(s, p) {if (p) ZFREE(s, p);} +/* Reverse the bytes in a 32-bit value */ +#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + #endif /* ZUTIL_H */ diff --git a/index.d b/index.d index 02c0686b515..60bdced6a4b 100644 --- a/index.d +++ b/index.d @@ -1,292 +1,332 @@ Ddoc -Phobos is the standard runtime library that comes with the -D language compiler. Also, check out the -wiki for Phobos. - - -

Philosophy

- - Each module in Phobos conforms as much as possible to the - following design goals. These are goals - rather than requirements because D is not a religion, - it's a programming language, and it recognizes that - sometimes the goals are contradictory and counterproductive - in certain situations, and programmers have - jobs that need to get done. - -
- - -
Machine and Operating System Independent Interfaces - -
It's pretty well accepted that gratuitous non-portability - should be avoided. This should not be - construed, however, as meaning that access to unusual - features of an operating system should be prevented. - - -
Simple Operations should be Simple - -
A common and simple operation, like writing an array of - bytes to a file, should be simple to - code. I haven't seen a class library yet that simply and efficiently - implemented common, basic file I/O operations. - - -
Classes should strive to be independent of one another - -
It's discouraging to pull in a megabyte of code bloat - by just trying to read a file into an array of - bytes. Class independence also means that classes that turn - out to be mistakes can be deprecated and redesigned without - forcing a rewrite of the rest of the class library. - - -
No pointless wrappers around C runtime library functions or OS API functions - -
D provides direct access to C runtime library functions - and operating system API functions. - Pointless D wrappers around those functions just adds blather, - bloat, baggage and bugs. - - -
Class implementations should use DBC - -
This will prove that DBC (Contract Programming) is worthwhile. - Not only will it aid in debugging the class, but - it will help every class user use the class correctly. - DBC in the class library will have great leverage. - - -
Use Exceptions for Error Handling - -
See Error Handling in D. - -
- -
-

Imports

- - Runtime library modules can be imported with the - import statement. Each module falls into one of several - packages: - -
-
std -
These are the core modules. -

- -

-
std.windows -
Modules specific to the Windows operating system. -

- -

std.linux -
Modules specific to the Linux operating system. -

- -

std.c -
Modules that are simply interfaces to C functions. - For example, interfaces to standard C library functions - will be in std.c, such as std.c.stdio would be the interface - to C's stdio.h. -

- -

-
std.c.windows -
Modules corresponding to the C Windows API functions. -

- -

std.c.linux -
Modules corresponding to the C Linux API functions. -

- -

-
- -
- -
-
etc -
This is the root of a hierarchy of modules mirroring the std - hierarchy. Modules in etc are not standard D modules. They are - here because they are experimental, or for some other reason are - not quite suitable for std, although they are still useful. -

-

- -
-

std: Core library modules

- -
- -
std.ascii -
Functions that operate on ASCII characters. - -
std.base64 -
Encode/decode base64 format. - -
std.bigint -
Arbitrary-precision ('bignum') arithmetic - -$(V1 -
std.bitarray -
Arrays of bits. - -
std.boxer -
Box/unbox types. -) -
std.compiler -
Information about the D compiler implementation. - -
std.conv -
Conversion of strings to integers. - -$(V1 -
std.date -
Date and time functions. Support locales. -) - -
std.datetime -
Date and time-related types and functions. - -
std.file -
Basic file operations like read, write, append. - -
std.format -
Formatted conversions of values to strings. - -$(V1 -
std.gc -
Control the garbage collector. -) -
std.math -
Include all the usual math functions like sin, cos, atan, etc. - -
std.md5 -
Compute MD5 digests. - -
std.mmfile -
Memory mapped files. - -
object -
The root class of the inheritance hierarchy - -
std.outbuffer -
Assemble data into an array of bytes - -
std.path -
Manipulate file names, path names, etc. - -
std.process -
Create/destroy threads. - -
std.random -
Random number generation. - -$(V1 -
std.recls -
Recursively search file system and (currently Windows - only) FTP sites. +$(P Phobos is the standard runtime library that comes with the D language +compiler.) + +$(P Generally, the $(D std) namespace is used for the main modules in the +Phobos standard library. The $(D etc) namespace is used for external C/C++ +library bindings. The $(D core) namespace is used for low-level D runtime +functions.) + +$(P The following table is a quick reference guide for which Phobos modules to +use for a given category of functionality. Note that some modules may appear in +more than one category, as some Phobos modules are quite generic and can be +applied in a variety of situations.) + +$(BOOKTABLE , + $(TR + $(TH Modules) + $(TH Description) + ) + $(LEADINGROW Algorithms & ranges) + $(TR + $(TD + $(LINK2 std_algorithm_package.html, std.algorithm)$(BR) + $(LINK2 std_range_package.html, std.range)$(BR) + $(LINK2 std_range_primitives.html, std.range.primitives)$(BR) + $(LINK2 std_range_interfaces.html, std.range.interfaces) + ) + $(TD Generic algorithms that work with $(LINK2 std_range.html, ranges) + of any type, including strings, arrays, and other kinds of + sequentially-accessed data. Algorithms include searching, + comparison, iteration, sorting, set operations, and mutation. + ) + ) + $(LEADINGROW Array manipulation) + $(TR + $(TD + $(LINK2 std_array.html, std.array)$(BR) + $(LINK2 std_algorithm_package.html, std.algorithm) + ) + $(TD Convenient operations commonly used with built-in arrays. + Note that many common array operations are subsets of more generic + algorithms that work with arbitrary ranges, so they are found in + $(D std.algorithm). + ) + ) + $(LEADINGROW Containers) + $(TR + $(TD + $(LINK2 std_container_array.html, std.container.array)$(BR) + $(LINK2 std_container_binaryheap.html, std.container.binaryheap)$(BR) + $(LINK2 std_container_dlist.html, std.container.dlist)$(BR) + $(LINK2 std_container_rbtree.html, std.container.rbtree)$(BR) + $(LINK2 std_container_slist.html, std.container.slist) + ) + $(TD See $(LINK2 std_container_package.html, std.container.*) for an + overview. + ) + ) + $(LEADINGROW Data formats) + $(TR + $(TD + $(LINK2 std_base64.html, std.base64)$(BR) + $(LINK2 std_csv.html, std.csv)$(BR) + $(LINK2 std_json.html, std.json)$(BR) + $(LINK2 std_xml.html, std.xml)$(BR) + $(LINK2 std_zip.html, std.zip)$(BR) + $(LINK2 std_zlib.html, std.zlib) + ) + $(TD Modules for reading/writing different data formats. + ) + ) + $(LEADINGROW Data integrity) + $(TR + $(TD + $(LINK2 std_digest_crc, std.digest.crc)$(BR) + $(LINK2 std_digest_digest, std.digest.digest)$(BR) + $(LINK2 std_digest_md, std.digest.md)$(BR) + $(LINK2 std_digest_ripemd, std.digest.ripemd)$(BR) + $(LINK2 std_digest_sha, std.digest.sha)$(BR) + ) + $(TD Hash algorithms for verifying data integrity. + ) + ) + $(LEADINGROW Date & time) + $(TR + $(TD + $(LINK2 std_datetime.html, std.datetime)$(BR) + $(LINK2 core_time.html, core.time) + ) + $(TD $(D std.datetime) provides convenient access to date and time + representations.$(BR) + $(D core.time) implements low-level time primitives. + ) + ) + $(LEADINGROW Exception handling) + $(TR + $(TD + $(LINK2 std_exception.html, std.exception)$(BR) + $(LINK2 core_exception.html, core.exception) + ) + $(TD $(D std.exception) implements routines related to exceptions. + $(D core.exception) defines built-in exception types and low-level + language hooks required by the compiler. + ) + ) + $(LEADINGROW External library bindings) + $(TR + $(TD + $(LINK2 etc_c_curl.html, etc.c.curl)$(BR) + $(LINK2 etc_c_sqlite3.html, etc.c.sqlite3)$(BR) + $(LINK2 etc_c_zlib.html, etc.c.zlib) + ) + $(TD Various bindings to external C libraries. + ) + ) + $(LEADINGROW I/O & File system) + $(TR + $(TD + $(LINK2 std_file.html, std.file)$(BR) + $(LINK2 std_path.html, std.path)$(BR) + $(LINK2 std_stdio.html, std.stdio) + ) + $(TD + $(D std.stdio) is the main module for I/O.$(BR) + $(D std.file) is for accessing the operating system's filesystem, + and $(D std.path) is for manipulating filesystem pathnames in a + platform-independent way.$(BR) + Note that $(D std.stream) and $(D std.cstream) are older, + deprecated modules scheduled to be replaced in the future; new + client code should avoid relying on them. + ) + ) + $(LEADINGROW Memory management) + $(TR + $(TD + $(LINK2 core_memory.html, core.memory)$(BR) + $(LINK2 std_typecons.html, std.typecons)$(BR) + ) + $(TD + $(D core.memory) provides an API for user code to control the + built-in garbage collector.$(BR) + $(D std.typecons) contains primitives for building scoped variables + and reference-counted types. + ) + ) + $(LEADINGROW Metaprogramming) + $(TR + $(TD + $(LINK2 std_traits.html, std.traits)$(BR) + $(LINK2 std_typecons.html, std.typecons)$(BR) + $(LINK2 std_typetuple.html, std.typetuple)$(BR) + $(LINK2 core_demangle.html, core.demangle) + ) + $(TD + These modules provide the primitives for compile-time introspection + and metaprogramming. + ) + ) + $(LEADINGROW Multitasking) + $(TR + $(TD + $(LINK2 std_concurrency, std.concurrency)$(BR) + $(LINK2 std_parallelism, std.parallelism)$(BR) + $(LINK2 std_process, std.process)$(BR) + $(LINK2 core_atomic, core.atomic)$(BR) + $(LINK2 core_sync_barrier, core.sync.barrier)$(BR) + $(LINK2 core_sync_condition, core.sync.condition)$(BR) + $(LINK2 core_sync_exception, core.sync.exception)$(BR) + $(LINK2 core_sync_mutex, core.sync.mutex)$(BR) + $(LINK2 core_sync_rwmutex, core.sync.rwmutex)$(BR) + $(LINK2 core_sync_semaphore, core.sync.semaphore)$(BR) + $(LINK2 core_thread, core.thread) + ) + $(TD These modules provide primitives for concurrent processing, + multithreading, synchronization, and interacting with operating + system processes.$(BR) + + $(D core.atomic) provides primitives for lock-free concurrent + programming.$(BR) + + $(D core.sync.*) modules provide low-level concurrent + programming building blocks.$(BR) + + $(D core.thread) implements multithreading primitives. + ) + ) + $(LEADINGROW Networking) + $(TR + $(TD + $(LINK2 std_socket.html, std.socket)$(BR) + $(LINK2 std_socketstream.html, std.socketstream)$(BR) + $(LINK2 std_net_curl.html, std.net.curl)$(BR) + $(LINK2 std_net_isemail.html, std.net.isemail) + ) + $(TD Utilities for networking. + ) + ) + $(LEADINGROW Numeric) + $(TR + $(TD + $(LINK2 std_bigint.html, std.bigint)$(BR) + $(LINK2 std_complex.html, std.complex)$(BR) + $(LINK2 std_math.html, std.math)$(BR) + $(LINK2 std_mathspecial.html, std.mathspecial)$(BR) + $(LINK2 std_numeric.html, std.numeric)$(BR) + $(LINK2 std_random.html, std.random) + ) + $(TD These modules provide the standard mathematical functions and + numerical algorithms.$(BR) + $(D std.bigint) provides an arbitrary-precision integer type.$(BR) + $(D std.complex) provides a complex number type.$(BR) + $(D std.random) provides pseudo-random number generators. + ) + ) + $(LEADINGROW Paradigms) + $(TR + $(TD + $(LINK2 std_functional, std.functional)$(BR) + $(LINK2 std_algorithm_package, std.algorithm)$(BR) + $(LINK2 std_signals, std.signals) + ) + $(TD $(D std.functional), along with the lazy algorithms of + $(D std.algorithm), provides utilities for writing functional-style + code in D.$(BR) + + $(D std.signals) provides a signal-and-slots framework for + event-driven programming. + ) + ) + $(LEADINGROW Runtime utilities) + $(TR + $(TD + $(LINK2 std_getopt.html, std.getopt)$(BR) + $(LINK2 std_compiler.html, std.compiler)$(BR) + $(LINK2 std_system.html, std.system)$(BR) + $(LINK2 core_cpuid.html, core.cpuid)$(BR) + $(LINK2 core_memory.html, core.memory)$(BR) + ) + $(TD Various modules for interacting with the execution environment and + compiler.$(BR) + $(D std.getopt) implements parsing of command-line arguments.$(BR) + $(D std.compiler) provides compiler information, mainly the + compiler vendor string and language version.$(BR) + $(D std.system) provides information about the runtime environment, + such as OS type and endianness.$(BR) + $(D core.cpuid) provides information on the capabilities of the + CPU the program is running on.$(BR) + $(D core.memory) allows user code to control the built-in garbage + collector. + ) + ) + $(LEADINGROW String manipulation) + $(TR + $(TD + $(LINK2 std_string.html, std.string)$(BR) + $(LINK2 std_array.html, std.array)$(BR) + $(LINK2 std_algorithm_package.html, std.algorithm)$(BR) + $(LINK2 std_uni, std.uni)$(BR) + $(LINK2 std_utf, std.utf)$(BR) + $(LINK2 std_format.html, std.format)$(BR) + $(LINK2 std_path.html, std.path)$(BR) + $(LINK2 std_regex.html, std.regex)$(BR) + $(LINK2 std_ascii, std.ascii)$(BR) + $(LINK2 std_encoding.html, std.encoding)$(BR) + $(LINK2 std_windows_charset.html, std.windows.charset) + ) + $(TD $(D std.string) contains functions that work specifically with + strings.$(BR) + + Many string manipulations are special cases of more generic + algorithms that work with general arrays, or generic ranges; these + are found in $(D std.array) and $(D std.algorithm).$(BR) + + D strings are encoded in Unicode; $(D std.uni) provides operations + that work with Unicode strings in general, while $(D std.utf) deals + with specific Unicode encodings and conversions between them.$(BR) + + $(D std.format) provides $(D printf)-style format string + formatting, with D's own improvements and extensions.$(BR) + + For manipulating filesystem pathnames, $(D std.path) is + provided.$(BR) + + $(D std.regex) is a very fast library for string matching and + substitution using regular expressions.$(BR) + + $(D std.ascii) provides routines specific to the ASCII subset of + Unicode. + + Windows-specific character set support is provided by + $(D std.windows.charset). + + Rudimentary support for other string encodings is provided by + $(D std.encoding). + ) + ) + $(LEADINGROW Type manipulations) + $(TR + $(TD + $(LINK2 std_conv.html, std.conv)$(BR) + $(LINK2 std_typecons.html, std.typecons)$(BR) + $(LINK2 std_bitmanip.html, std.bitmanip)$(BR) + $(LINK2 core_bitop.html, core.bitop)$(BR) + ) + $(TD $(D std.conv) provides powerful automatic conversions between + built-in types as well as user-defined types that implement + standard conversion primitives.$(BR) + + $(D std.typecons) provides various utilities for type construction + and compile-time type introspection. It provides facilities for + constructing scoped variables and reference-counted types, as well + as miscellaneous useful generic types such as tuples and + flags.$(BR) + + $(D std.bitmanip) provides various bit-level operations, bit + arrays, and bit fields. $(D core.bitop) provides low-level bit + manipulation primitives.$(BR) + ) + ) + $(LEADINGROW Vector programming) + $(TR + $(TD + $(LINK2 core_simd, core.simd)$(BR) + ) + $(TD The $(D core.simd) module provides access to SIMD intrinsics in + the compiler.) + ) ) -
std.regex -
The usual regular expression functions. - -
std.socket -
Sockets. - -
std.socketstream -
Stream for a blocking, connected Socket. - -
std.stdint -
Integral types for various purposes. - -
std.stdio -
Standard I/O. - -
std.cstream -
Stream I/O. - -
std.stream -
Stream I/O. - -
std.string -
Basic string operations not covered by array ops. - -
std.system -
Inquire about the CPU, operating system. - - - -
std.uni -
Functions that operate on Unicode characters. - -
std.uri -
Encode and decode Uniform Resource Identifiers (URIs). - -
std.utf -
Encode and decode utf character encodings. - -
std.zip -
Read/write zip archives. - -
std.zlib -
Compression / Decompression of data. - -
- -
-

std.windows: Modules specific to the Windows operating system

- -
- -
std.windows.syserror -
Convert Windows error codes to strings. - -
- -
-

std.linux: Modules specific to the Linux operating system

- -
-

std.c: Interface to C functions

- -
- -
std.c.stdio -
Interface to C stdio functions like printf(). - -
- -
-

std.c.windows: Interface to C Windows functions

- -
- -
std.c.windows.windows -
Interface to Windows APIs - -
- -
-

std.c.linux: Interface to C Linux functions

- -
- -
std.c.linux.linux -
Interface to Linux APIs - -
- -
-

std.c.stdio

- -
-
int printf(char* format, ...) -
C printf() function. -
Macros: TITLE=Phobos Runtime Library diff --git a/posix.mak b/posix.mak index 27dbd8fb1e4..f4055057e2c 100644 --- a/posix.mak +++ b/posix.mak @@ -53,7 +53,11 @@ ifeq (,$(OS)) endif ifeq (,$(MODEL)) - uname_M:=$(shell uname -m) + ifeq ($(OS),solaris) + uname_M:=$(shell isainfo -n) + else + uname_M:=$(shell uname -m) + endif ifneq (,$(findstring $(uname_M),x86_64 amd64)) MODEL:=64 endif @@ -87,17 +91,20 @@ DOCSRC = ../dlang.org WEBSITE_DIR = ../web DOC_OUTPUT_DIR = $(WEBSITE_DIR)/phobos-prerelease BIGDOC_OUTPUT_DIR = /tmp -SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(EXTRA_DOCUMENTABLES)) -STDDOC = $(DOCSRC)/std.ddoc -BIGSTDDOC = $(DOCSRC)/std_consolidated.ddoc +SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) std/regex/package $(EXTRA_DOCUMENTABLES) $(STD_LOGGER_MODULES)) +STDDOC = $(DOCSRC)/html.ddoc $(DOCSRC)/dlang.org.ddoc $(DOCSRC)/std_navbar-prerelease.ddoc $(DOCSRC)/std.ddoc $(DOCSRC)/macros.ddoc +BIGSTDDOC = $(DOCSRC)/std_consolidated.ddoc $(DOCSRC)/macros.ddoc # Set DDOC, the documentation generator DDOC=$(DMD) -m$(MODEL) -w -c -o- -version=StdDdoc \ - -I$(DRUNTIME_PATH)/import $(DMDEXTRAFLAGS) + -I$(DRUNTIME_PATH)/import $(DMDEXTRAFLAGS) # Set DRUNTIME name and full path +ifneq (,$(DRUNTIME)) + CUSTOM_DRUNTIME=1 +endif ifeq (,$(findstring win,$(OS))) DRUNTIME = $(DRUNTIME_PATH)/lib/libdruntime-$(OS)$(MODEL).a - DRUNTIMESO = $(DRUNTIME_PATH)/lib/libdruntime-$(OS)$(MODEL)so.a + DRUNTIMESO = $(basename $(DRUNTIME))so.a else DRUNTIME = $(DRUNTIME_PATH)/lib/druntime.lib endif @@ -129,7 +136,7 @@ ifneq (,$(filter cc% gcc% clang% icc% egcc%, $(CC))) endif # Set DFLAGS -DFLAGS=-I$(DRUNTIME_PATH)/import $(DMDEXTRAFLAGS) -w -m$(MODEL) $(PIC) +DFLAGS=-I$(DRUNTIME_PATH)/import $(DMDEXTRAFLAGS) -w -dip25 -m$(MODEL) $(PIC) ifeq ($(BUILD),debug) DFLAGS += -g -debug else @@ -175,21 +182,32 @@ LINKCURL:=$(if $(LIBCURL_STUB),-L$(LIBCURL_STUB),-L-lcurl) MAIN = $(ROOT)/emptymain.d # Stuff in std/ -STD_MODULES = $(addprefix std/, algorithm array ascii base64 bigint \ - bitmanip compiler complex concurrency conv \ - cstream csv datetime demangle encoding exception \ - file format functional getopt json math mathspecial \ - mmfile numeric outbuffer parallelism path \ - process random range regex signals socket socketstream \ - stdint stdio stdiobase stream string syserror system traits \ - typecons typetuple uni uri utf uuid variant xml zip zlib) +STD_MODULES = $(addprefix std/, array ascii base64 bigint \ + bitmanip compiler complex concurrency conv \ + cstream csv datetime demangle encoding exception \ + file format functional getopt json math mathspecial \ + metastrings mmfile numeric outbuffer parallelism path \ + process random signals socket socketstream \ + stdint stdio stdiobase stream string syserror system traits \ + typecons typetuple uni uri utf uuid variant xml zip zlib) STD_NET_MODULES = $(addprefix std/net/, isemail curl) +STD_LOGGER_MODULES = $(addprefix std/experimental/logger/, package core \ + filelogger nulllogger multilogger) + +STD_REGEX_MODULES = $(addprefix std/regex/, package $(addprefix internal/, \ + generator ir parser backtracking kickstart tests thompson)) + +STD_ALGO_MODULES = $(addprefix std/algorithm/, package comparison iteration \ + mutation searching setops sorting) + +STD_RANGE_MODULES = $(addprefix std/range/, package primitives interfaces) + STD_DIGEST_MODULES = $(addprefix std/digest/, digest crc md ripemd sha) STD_CONTAINER_MODULES = $(addprefix std/container/, package array \ - binaryheap dlist rbtree slist util) + binaryheap dlist rbtree slist util) # OS-specific D modules EXTRA_MODULES_LINUX := $(addprefix std/c/linux/, linux socket) @@ -212,11 +230,14 @@ EXTRA_MODULES += $(EXTRA_DOCUMENTABLES) $(addprefix \ std/internal/math/, biguintcore biguintnoasm biguintx86 \ gammafunction errorfunction summation) $(addprefix std/internal/, \ cstring processinit unicode_tables scopebuffer\ - unicode_comp unicode_decomp unicode_grapheme unicode_norm) + unicode_comp unicode_decomp unicode_grapheme unicode_norm) \ + $(addprefix std/internal/test/, dummyrange) # Aggregate all D modules relevant to this build D_MODULES = $(STD_MODULES) $(EXTRA_MODULES) $(STD_NET_MODULES) \ - $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) + $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_REGEX_MODULES) \ + $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) $(STD_LOGGER_MODULES) + # Add the .d suffix to the module names D_FILES = $(addsuffix .d,$(D_MODULES)) # Aggregate all D modules over all OSs (this is for the zip file) @@ -301,8 +322,8 @@ $(ROOT_OF_THEM_ALL)/osx/release/libphobos2.a: $(MAKE) -f $(MAKEFILE) OS=$(OS) MODEL=32 BUILD=release $(MAKE) -f $(MAKEFILE) OS=$(OS) MODEL=64 BUILD=release lipo $(ROOT_OF_THEM_ALL)/osx/release/32/libphobos2.a \ - $(ROOT_OF_THEM_ALL)/osx/release/64/libphobos2.a \ - -create -output $@ + $(ROOT_OF_THEM_ALL)/osx/release/64/libphobos2.a \ + -create -output $@ endif $(addprefix $(ROOT)/unittest/,$(DISABLED_TESTS)) : @@ -372,6 +393,9 @@ endif cp -r etc/* $(INSTALL_DIR)/src/phobos/etc/ cp LICENSE_1_0.txt $(INSTALL_DIR)/phobos-LICENSE.txt +ifeq (1,$(CUSTOM_DRUNTIME)) +# We consider a custom-set DRUNTIME a sign they build druntime themselves +else # This rule additionally produces $(DRUNTIMESO). Add a fake dependency # to always invoke druntime's make. Use FORCE instead of .PHONY to # avoid rebuilding phobos when $(DRUNTIME) didn't change. @@ -384,6 +408,8 @@ endif FORCE: +endif + ########################################################### # html documentation @@ -410,9 +436,21 @@ $(DOC_OUTPUT_DIR)/std_c_windows_%.html : std/c/windows/%.d $(STDDOC) $(DOC_OUTPUT_DIR)/std_container_%.html : std/container/%.d $(STDDOC) $(DDOC) project.ddoc $(STDDOC) -Df$@ $< +$(DOC_OUTPUT_DIR)/std_algorithm_%.html : std/algorithm/%.d $(STDDOC) + $(DDOC) project.ddoc $(STDDOC) -Df$@ $< + +$(DOC_OUTPUT_DIR)/std_range_%.html : std/range/%.d $(STDDOC) + $(DDOC) project.ddoc $(STDDOC) -Df$@ $< + +$(DOC_OUTPUT_DIR)/std_regex_%.html : std/regex/%.d $(STDDOC) + $(DDOC) project.ddoc $(STDDOC) -Df$@ $< + $(DOC_OUTPUT_DIR)/std_net_%.html : std/net/%.d $(STDDOC) $(DDOC) project.ddoc $(STDDOC) -Df$@ $< +$(DOC_OUTPUT_DIR)/std_experimental_logger_%.html : std/experimental/logger/%.d $(STDDOC) + $(DDOC) project.ddoc $(STDDOC) -Df$@ $< + $(DOC_OUTPUT_DIR)/std_digest_%.html : std/digest/%.d $(STDDOC) $(DDOC) project.ddoc $(STDDOC) -Df$@ $< @@ -424,6 +462,9 @@ $(DOC_OUTPUT_DIR)/%.html : %.d $(STDDOC) html : $(DOC_OUTPUT_DIR)/. $(HTMLS) $(STYLECSS_TGT) +allmod : + echo $(SRC_DOCUMENTABLES) + rsync-prerelease : html rsync -avz $(DOC_OUTPUT_DIR)/ d-programming@digitalmars.com:data/phobos-prerelease/ rsync -avz $(WEBSITE_DIR)/ d-programming@digitalmars.com:data/phobos-prerelase/ diff --git a/std/algorithm.d b/std/algorithm.d deleted file mode 100644 index 7a00f1a587b..00000000000 --- a/std/algorithm.d +++ /dev/null @@ -1,13603 +0,0 @@ -// Written in the D programming language. - -/** - - -$(BOOKTABLE , -$(TR $(TH Category) $(TH Functions) -) -$(TR $(TDNW Searching) $(TD $(MYREF all) $(MYREF any) $(MYREF balancedParens) $(MYREF -boyerMooreFinder) $(MYREF canFind) $(MYREF commonPrefix) $(MYREF count) -$(MYREF countUntil) $(MYREF endsWith) $(MYREF find) $(MYREF -findAdjacent) $(MYREF findAmong) $(MYREF findSkip) $(MYREF findSplit) -$(MYREF findSplitAfter) $(MYREF findSplitBefore) $(MYREF minCount) -$(MYREF minPos) $(MYREF mismatch) $(MYREF skipOver) $(MYREF startsWith) -$(MYREF until) ) -) -$(TR $(TDNW Comparison) $(TD $(MYREF among) $(MYREF cmp) $(MYREF equal) $(MYREF -levenshteinDistance) $(MYREF levenshteinDistanceAndPath) $(MYREF max) -$(MYREF min) $(MYREF mismatch) $(MYREF clamp) $(MYREF predSwitch) ) -) -$(TR $(TDNW Iteration) $(TD $(MYREF filter) $(MYREF filterBidirectional) -$(MYREF group) $(MYREF joiner) $(MYREF map) $(MYREF reduce) $(MYREF -splitter) $(MYREF sum) $(MYREF uniq) ) -) -$(TR $(TDNW Sorting) $(TD $(MYREF completeSort) $(MYREF isPartitioned) -$(MYREF isSorted) $(MYREF makeIndex) $(MYREF multiSort) $(MYREF nextPermutation) -$(MYREF nextEvenPermutation) $(MYREF partialSort) $(MYREF -partition) $(MYREF partition3) $(MYREF schwartzSort) $(MYREF sort) -$(MYREF topN) $(MYREF topNCopy) ) -) -$(TR $(TDNW Set operations) $(TD $(MYREF cartesianProduct) $(MYREF -largestPartialIntersection) $(MYREF largestPartialIntersectionWeighted) -$(MYREF nWayUnion) $(MYREF setDifference) $(MYREF setIntersection) $(MYREF -setSymmetricDifference) $(MYREF setUnion) ) -) -$(TR $(TDNW Mutation) $(TD $(MYREF bringToFront) $(MYREF copy) $(MYREF -fill) $(MYREF initializeAll) $(MYREF move) $(MYREF moveAll) $(MYREF -moveSome) $(MYREF remove) $(MYREF reverse) $(MYREF strip) $(MYREF stripLeft) -$(MYREF stripRight) $(MYREF swap) $(MYREF swapRanges) $(MYREF uninitializedFill) ) -) -$(TR $(TDNW Utility) $(TD $(MYREF forward) )) -) - -Implements algorithms oriented mainly towards processing of -sequences. Some functions are semantic equivalents or supersets of -those found in the $(D $(LESS)_algorithm$(GREATER)) header in $(WEB -sgi.com/tech/stl/, Alexander Stepanov's Standard Template Library) for -C++. Sequences processed by these functions define range-based interfaces. - -$(LINK2 std_range.html, Reference on ranges)$(BR) -$(LINK2 http://ddili.org/ders/d.en/ranges.html, Tutorial on ranges) - -Many functions in this module are parameterized with a function or a -$(GLOSSARY predicate). The predicate may be passed either as a -function name, a delegate name, a $(GLOSSARY functor) name, or a -compile-time string. The string may consist of $(B any) legal D -expression that uses the symbol $(D a) (for unary functions) or the -symbols $(D a) and $(D b) (for binary functions). These names will NOT -interfere with other homonym symbols in user code because they are -evaluated in a different context. The default for all binary -comparison predicates is $(D "a == b") for unordered operations and -$(D "a < b") for ordered operations. - -Example: - ----- -int[] a = ...; -static bool greater(int a, int b) -{ - return a > b; -} -sort!(greater)(a); // predicate as alias -sort!("a > b")(a); // predicate as string - // (no ambiguity with array name) -sort(a); // no predicate, "a < b" is implicit ----- - -$(BOOKTABLE Cheat Sheet, -$(TR $(TH Function Name) $(TH Description) -) -$(LEADINGROW Searching -) -$(TR $(TDNW $(LREF all)) $(TD $(D all!"a > 0"([1, 2, 3, 4])) returns $(D true) because all elements are positive) -) -$(TR $(TDNW $(LREF any)) $(TD $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one element is positive) -) -$(TR $(TDNW $(LREF balancedParens)) $(TD $(D -balancedParens("((1 + 1) / 2)")) returns $(D true) because the string -has balanced parentheses.) -) -$(TR $(TDNW $(LREF boyerMooreFinder)) $(TD $(D find("hello -world", boyerMooreFinder("or"))) returns $(D "orld") using the $(LUCKY -Boyer-Moore _algorithm).) -) -$(TR $(TDNW $(LREF canFind)) $(TD $(D canFind("hello world", -"or")) returns $(D true).) -) -$(TR $(TDNW $(LREF count)) $(TD Counts elements that are equal -to a specified value or satisfy a predicate. $(D count([1, 2, 1], 1)) -returns $(D 2) and $(D count!"a < 0"([1, -3, 0])) returns $(D 1).) -) -$(TR $(TDNW $(LREF countUntil)) $(TD $(D countUntil(a, b)) -returns the number of steps taken in $(D a) to reach $(D b); for -example, $(D countUntil("hello!", "o")) returns $(D 4).) -) -$(TR $(TDNW $(LREF commonPrefix)) $(TD $(D commonPrefix("parakeet", -"parachute")) returns $(D "para").) -) -$(TR $(TDNW $(LREF endsWith)) $(TD $(D endsWith("rocks", "ks")) -returns $(D true).) -) -$(TR $(TD $(LREF find)) $(TD $(D find("hello world", -"or")) returns $(D "orld") using linear search. (For binary search refer -to $(XREF range,sortedRange).)) -) -$(TR $(TDNW $(LREF findAdjacent)) $(TD $(D findAdjacent([1, 2, -3, 3, 4])) returns the subrange starting with two equal adjacent -elements, i.e. $(D [3, 3, 4]).) -) -$(TR $(TDNW $(LREF findAmong)) $(TD $(D findAmong("abcd", -"qcx")) returns $(D "cd") because $(D 'c') is among $(D "qcx").) -) -$(TR $(TDNW $(LREF findSkip)) $(TD If $(D a = "abcde"), then -$(D findSkip(a, "x")) returns $(D false) and leaves $(D a) unchanged, -whereas $(D findSkip(a, 'c')) advances $(D a) to $(D "cde") and -returns $(D true).) -) -$(TR $(TDNW $(LREF findSplit)) $(TD $(D findSplit("abcdefg", -"de")) returns the three ranges $(D "abc"), $(D "de"), and $(D -"fg").) -) -$(TR $(TDNW $(LREF findSplitAfter)) $(TD $(D -findSplitAfter("abcdefg", "de")) returns the two ranges $(D "abcde") -and $(D "fg").) -) -$(TR $(TDNW $(LREF findSplitBefore)) $(TD $(D -findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc") and -$(D "defg").) -) -$(TR $(TDNW $(LREF minCount)) $(TD $(D minCount([2, 1, 1, 4, -1])) returns $(D tuple(1, 3)).) -) -$(TR $(TDNW $(LREF minPos)) $(TD $(D minPos([2, 3, 1, 3, 4, -1])) returns the subrange $(D [1, 3, 4, 1]), i.e., positions the range -at the first occurrence of its minimal element.) -) -$(TR $(TDNW $(LREF mismatch)) $(TD $(D mismatch("parakeet", "parachute")) -returns the two ranges $(D "keet") and $(D "chute").) -) -$(TR $(TDNW $(LREF skipOver)) $(TD Assume $(D a = "blah"). Then -$(D skipOver(a, "bi")) leaves $(D a) unchanged and returns $(D false), -whereas $(D skipOver(a, "bl")) advances $(D a) to refer to $(D "ah") -and returns $(D true).) -) -$(TR $(TDNW $(LREF startsWith)) $(TD $(D startsWith("hello, -world", "hello")) returns $(D true).) -) -$(TR $(TDNW $(LREF until)) $(TD Lazily iterates a range -until a specific value is found.) -) -$(LEADINGROW Comparison -) -$(TR $(TDNW $(LREF among)) $(TD Checks if a value is among a set -of values, e.g. $(D if (v.among(1, 2, 3)) // `v` is 1, 2 or 3)) -) -$(TR $(TDNW $(LREF cmp)) $(TD $(D cmp("abc", "abcd")) is $(D --1), $(D cmp("abc", "aba")) is $(D 1), and $(D cmp("abc", "abc")) is -$(D 0).) -) -$(TR $(TDNW $(LREF equal)) $(TD Compares ranges for -element-by-element equality, e.g. $(D equal([1, 2, 3], [1.0, 2.0, -3.0])) returns $(D true).) -) -$(TR $(TDNW $(LREF levenshteinDistance)) $(TD $(D -levenshteinDistance("kitten", "sitting")) returns $(D 3) by using the -$(LUCKY Levenshtein distance _algorithm).) -) -$(TR $(TDNW $(LREF levenshteinDistanceAndPath)) $(TD $(D -levenshteinDistanceAndPath("kitten", "sitting")) returns $(D tuple(3, -"snnnsni")) by using the $(LUCKY Levenshtein distance _algorithm).) -) -$(TR $(TDNW $(LREF max)) $(TD $(D max(3, 4, 2)) returns $(D -4).) -) -$(TR $(TDNW $(LREF min)) $(TD $(D min(3, 4, 2)) returns $(D -2).) -) -$(TR $(TDNW $(LREF clamp)) $(TD $(D clamp(1, 3, 6)) returns $(D -3). $(D clamp(4, 3, 6)) return $(D 4).) -) -$(TR $(TDNW $(LREF mismatch)) $(TD $(D mismatch("oh hi", -"ohayo")) returns $(D tuple(" hi", "ayo")).) -) -$(TR $(TDNW $(LREF predSwitch)) $(TD 2.predSwitch(1, "one", 2, "two", 3, -"three") returns $(D "two").) -) -$(LEADINGROW Iteration -) -$(TR $(TDNW $(LREF filter)) $(TD $(D filter!"a > 0"([1, -1, 2, -0, -3])) iterates over elements $(D 1) and $(D 2).) -) -$(TR $(TDNW $(LREF filterBidirectional)) $(TD Similar to $(D -filter), but also provides $(D back) and $(D popBack) at a small -increase in cost.) -) -$(TR $(TDNW $(LREF group)) $(TD $(D group([5, 2, 2, 3, 3])) -returns a range containing the tuples $(D tuple(5, 1)), -$(D tuple(2, 2)), and $(D tuple(3, 2)).) -) -$(TR $(TDNW $(LREF joiner)) $(TD $(D joiner(["hello", -"world!"], "; ")) returns a range that iterates over the characters $(D -"hello; world!"). No new string is created - the existing inputs are -iterated.) -) -$(TR $(TDNW $(LREF map)) $(TD $(D map!"2 * a"([1, 2, 3])) -lazily returns a range with the numbers $(D 2), $(D 4), $(D 6).) -) -$(TR $(TDNW $(LREF reduce)) $(TD $(D reduce!"a + b"([1, 2, 3, -4])) returns $(D 10).) -) -$(TR $(TDNW $(LREF splitter)) $(TD Lazily splits a range by a -separator.) -) -$(TR $(TDNW $(LREF sum)) $(TD Same as $(D reduce), but specialized for -accurate summation.) -) -$(TR $(TDNW $(LREF uniq)) $(TD Iterates over the unique elements -in a range, which is assumed sorted.) -) -$(LEADINGROW Sorting -) -$(TR $(TDNW $(LREF completeSort)) $(TD If $(D a = [10, 20, 30]) -and $(D b = [40, 6, 15]), then $(D completeSort(a, b)) leaves $(D a = -[6, 10, 15]) and $(D b = [20, 30, 40]). The range $(D a) must be -sorted prior to the call, and as a result the combination $(D $(XREF -range,chain)(a, b)) is sorted.) -) -$(TR $(TDNW $(LREF isPartitioned)) $(TD $(D isPartitioned!"a < -0"([-1, -2, 1, 0, 2])) returns $(D true) because the predicate is $(D -true) for a portion of the range and $(D false) afterwards.) -) -$(TR $(TDNW $(LREF isSorted)) $(TD $(D isSorted([1, 1, 2, 3])) -returns $(D true).) -) -$(TR $(TDNW $(LREF makeIndex)) $(TD Creates a separate index -for a range.) -) -$(TR $(TDNW $(LREF nextPermutation)) $(TD Computes the next lexicographically -greater permutation of a range in-place.) -) -$(TR $(TDNW $(LREF nextEvenPermutation)) $(TD Computes the next -lexicographically greater even permutation of a range in-place.) -) -$(TR $(TDNW $(LREF partialSort)) $(TD If $(D a = [5, 4, 3, 2, -1]), then $(D partialSort(a, 3)) leaves $(D a[0 .. 3] = [1, 2, -3]). The other elements of $(D a) are left in an unspecified order.) -) -$(TR $(TDNW $(LREF partition)) $(TD Partitions a range -according to a predicate.) -) -$(TR $(TDNW $(LREF partition3)) $(TD Partitions a range -in three parts (less than, equal, greater than the given pivot).) -) -$(TR $(TDNW $(LREF schwartzSort)) $(TD Sorts with the help of -the $(LUCKY Schwartzian transform).) -) -$(TR $(TDNW $(LREF sort)) $(TD Sorts.) -) -$(TR $(TDNW $(LREF topN)) $(TD Separates the top elements in a -range.) -) -$(TR $(TDNW $(LREF topNCopy)) $(TD Copies out the top elements -of a range.) -) -$(LEADINGROW Set operations -) -$(TR $(TDNW $(LREF cartesianProduct)) $(TD Computes Cartesian product of two -ranges.) -) -$(TR $(TDNW $(LREF largestPartialIntersection)) $(TD Copies out -the values that occur most frequently in a range of ranges.) -) -$(TR $(TDNW $(LREF largestPartialIntersectionWeighted)) $(TD -Copies out the values that occur most frequently (multiplied by -per-value weights) in a range of ranges.) -) -$(TR $(TDNW $(LREF nWayUnion)) $(TD Computes the union of a set -of sets implemented as a range of sorted ranges.) -) -$(TR $(TDNW $(LREF setDifference)) $(TD Lazily computes the set -difference of two or more sorted ranges.) -) -$(TR $(TDNW $(LREF setIntersection)) $(TD Lazily computes the -intersection of two or more sorted ranges.) -) -$(TR $(TDNW $(LREF setSymmetricDifference)) $(TD Lazily -computes the symmetric set difference of two or more sorted ranges.) -) -$(TR $(TDNW $(LREF setUnion)) $(TD Lazily computes the set -union of two or more sorted ranges.) -) -$(LEADINGROW Mutation -) -$(TR $(TDNW $(LREF bringToFront)) $(TD If $(D a = [1, 2, 3]) -and $(D b = [4, 5, 6, 7]), $(D bringToFront(a, b)) leaves $(D a = [4, -5, 6]) and $(D b = [7, 1, 2, 3]).) -) -$(TR $(TDNW $(LREF copy)) $(TD Copies a range to another. If -$(D a = [1, 2, 3]) and $(D b = new int[5]), then $(D copy(a, b)) -leaves $(D b = [1, 2, 3, 0, 0]) and returns $(D b[3 .. $]).) -) -$(TR $(TDNW $(LREF fill)) $(TD Fills a range with a pattern, -e.g., if $(D a = new int[3]), then $(D fill(a, 4)) leaves $(D a = [4, -4, 4]) and $(D fill(a, [3, 4])) leaves $(D a = [3, 4, 3]).) -) -$(TR $(TDNW $(LREF initializeAll)) $(TD If $(D a = [1.2, 3.4]), -then $(D initializeAll(a)) leaves $(D a = [double.init, -double.init]).) -) -$(TR $(TDNW $(LREF move)) $(TD $(D move(a, b)) moves $(D a) -into $(D b). $(D move(a)) reads $(D a) destructively.) -) -$(TR $(TDNW $(LREF moveAll)) $(TD Moves all elements from one -range to another.) -) -$(TR $(TDNW $(LREF moveSome)) $(TD Moves as many elements as -possible from one range to another.) -) -$(TR $(TDNW $(LREF remove)) $(TD Removes elements from a range -in-place, and returns the shortened range.) -) -$(TR $(TDNW $(LREF reverse)) $(TD If $(D a = [1, 2, 3]), $(D -reverse(a)) changes it to $(D [3, 2, 1]).) -) -$(TR $(TDNW $(LREF strip)) $(TD Strips all leading and trailing -elements equal to a value, or that satisfy a predicate. -If $(D a = [1, 1, 0, 1, 1]), then $(D strip(a, 1)) and $(D strip!(e => e == 1)(a)) -returns $(D [0]).) -) -$(TR $(TDNW $(LREF stripLeft)) $(TD Strips all leading elements equal to a value, -or that satisfy a predicate. -If $(D a = [1, 1, 0, 1, 1]), then $(D stripLeft(a, 1)) and $(D stripLeft!(e => e == 1)(a)) -returns $(D [0, 1, 1]).) -) -$(TR $(TDNW $(LREF stripRight)) $(TD Strips all trailing elements equal to a value, -or that satisfy a predicate. -If $(D a = [1, 1, 0, 1, 1]), then $(D stripRight(a, 1)) and $(D stripRight!(e => e == 1)(a)) -returns $(D [1, 1, 0]).) -) -$(TR $(TDNW $(LREF swap)) $(TD Swaps two values.) -) -$(TR $(TDNW $(LREF swapRanges)) $(TD Swaps all elements of two -ranges.) -) -$(TR $(TDNW $(LREF uninitializedFill)) $(TD Fills a range -(assumed uninitialized) with a value.) -) -) - -Macros: -WIKI = Phobos/StdAlgorithm -MYREF = $1  - -Copyright: Andrei Alexandrescu 2008-. - -License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). - -Authors: $(WEB erdani.com, Andrei Alexandrescu) - -Source: $(PHOBOSSRC std/_algorithm.d) - */ -module std.algorithm; -//debug = std_algorithm; - -import std.functional : unaryFun, binaryFun; -import std.range; -import std.traits; -import std.typecons : tuple, Tuple; -import std.typetuple : TypeTuple, staticMap, allSatisfy, anySatisfy; - -version(unittest) -{ - debug(std_algorithm) import std.stdio; - mixin(dummyRanges); -} - -private T* addressOf(T)(ref T val) { return &val; } - -// Same as std.string.format, but "self-importing". -// Helps reduce code and imports, particularly in static asserts. -// Also helps with missing imports errors. -private template algoFormat() -{ - import std.string : format; - alias algoFormat = std.string.format; -} - -/** -$(D auto map(Range)(Range r) if (isInputRange!(Unqual!Range));) - -Implements the homonym function (also known as $(D transform)) present -in many languages of functional flavor. The call $(D map!(fun)(range)) -returns a range of which elements are obtained by applying $(D fun(x)) -left to right for all $(D x) in $(D range). The original ranges are -not changed. Evaluation is done lazily. -*/ -template map(fun...) if (fun.length >= 1) -{ - auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) - { - alias AppliedReturnType(alias f) = typeof(f(r.front)); - - static if (fun.length > 1) - { - import std.functional : adjoin; - import std.typetuple : staticIndexOf; - - alias _funs = staticMap!(unaryFun, fun); - alias _fun = adjoin!_funs; - - alias ReturnTypes = staticMap!(AppliedReturnType, _funs); - static assert(staticIndexOf!(void, ReturnTypes) == -1, - "All mapping functions must not return void."); - } - else - { - alias _fun = unaryFun!fun; - - static assert(!is(AppliedReturnType!_fun == void), - "Mapping function must not return void."); - } - - return MapResult!(_fun, Range)(r); - } -} - -/// -unittest -{ - int[] arr1 = [ 1, 2, 3, 4 ]; - int[] arr2 = [ 5, 6 ]; - auto squares = map!(a => a * a)(chain(arr1, arr2)); - assert(equal(squares, [ 1, 4, 9, 16, 25, 36 ])); -} - -/** -Multiple functions can be passed to $(D map). In that case, the -element type of $(D map) is a tuple containing one element for each -function. -*/ -unittest -{ - auto sums = [2, 4, 6, 8]; - auto products = [1, 4, 9, 16]; - - size_t i = 0; - foreach (result; [ 1, 2, 3, 4 ].map!("a + a", "a * a")) - { - assert(result[0] == sums[i]); - assert(result[1] == products[i]); - ++i; - } -} - -/** -You may alias $(D map) with some function(s) to a symbol and use -it separately: -*/ -unittest -{ - import std.conv : to; - - alias stringize = map!(to!string); - assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); -} - -private struct MapResult(alias fun, Range) -{ - alias R = Unqual!Range; - R _input; - - static if (isBidirectionalRange!R) - { - @property auto ref back()() - { - return fun(_input.back); - } - - void popBack()() - { - _input.popBack(); - } - } - - this(R input) - { - _input = input; - } - - static if (isInfinite!R) - { - // Propagate infinite-ness. - enum bool empty = false; - } - else - { - @property bool empty() - { - return _input.empty; - } - } - - void popFront() - { - _input.popFront(); - } - - @property auto ref front() - { - return fun(_input.front); - } - - static if (isRandomAccessRange!R) - { - static if (is(typeof(_input[ulong.max]))) - private alias opIndex_t = ulong; - else - private alias opIndex_t = uint; - - auto ref opIndex(opIndex_t index) - { - return fun(_input[index]); - } - } - - static if (hasLength!R) - { - @property auto length() - { - return _input.length; - } - - alias opDollar = length; - } - - static if (hasSlicing!R) - { - static if (is(typeof(_input[ulong.max .. ulong.max]))) - private alias opSlice_t = ulong; - else - private alias opSlice_t = uint; - - static if (hasLength!R) - { - auto opSlice(opSlice_t low, opSlice_t high) - { - return typeof(this)(_input[low .. high]); - } - } - else static if (is(typeof(_input[opSlice_t.max .. $]))) - { - struct DollarToken{} - enum opDollar = DollarToken.init; - auto opSlice(opSlice_t low, DollarToken) - { - return typeof(this)(_input[low .. $]); - } - - auto opSlice(opSlice_t low, opSlice_t high) - { - return this[low .. $].take(high - low); - } - } - } - - static if (isForwardRange!R) - { - @property auto save() - { - return typeof(this)(_input.save); - } - } -} - -unittest -{ - import std.conv : to; - import std.functional : adjoin; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - alias stringize = map!(to!string); - assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); - - uint counter; - alias count = map!((a) { return counter++; }); - assert(equal(count([ 10, 2, 30, 4 ]), [ 0, 1, 2, 3 ])); - - counter = 0; - adjoin!((a) { return counter++; }, (a) { return counter++; })(1); - alias countAndSquare = map!((a) { return counter++; }, (a) { return counter++; }); - //assert(equal(countAndSquare([ 10, 2 ]), [ tuple(0u, 100), tuple(1u, 4) ])); -} - -unittest -{ - import std.ascii : toUpper; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] arr1 = [ 1, 2, 3, 4 ]; - const int[] arr1Const = arr1; - int[] arr2 = [ 5, 6 ]; - auto squares = map!("a * a")(arr1Const); - assert(squares[$ - 1] == 16); - assert(equal(squares, [ 1, 4, 9, 16 ][])); - assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][])); - - // Test the caching stuff. - assert(squares.back == 16); - auto squares2 = squares.save; - assert(squares2.back == 16); - - assert(squares2.front == 1); - squares2.popFront(); - assert(squares2.front == 4); - squares2.popBack(); - assert(squares2.front == 4); - assert(squares2.back == 9); - - assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][])); - - uint i; - foreach (e; map!("a", "a * a")(arr1)) - { - assert(e[0] == ++i); - assert(e[1] == i * i); - } - - // Test length. - assert(squares.length == 4); - assert(map!"a * a"(chain(arr1, arr2)).length == 6); - - // Test indexing. - assert(squares[0] == 1); - assert(squares[1] == 4); - assert(squares[2] == 9); - assert(squares[3] == 16); - - // Test slicing. - auto squareSlice = squares[1..squares.length - 1]; - assert(equal(squareSlice, [4, 9][])); - assert(squareSlice.back == 9); - assert(squareSlice[1] == 9); - - // Test on a forward range to make sure it compiles when all the fancy - // stuff is disabled. - auto fibsSquares = map!"a * a"(recurrence!("a[n-1] + a[n-2]")(1, 1)); - assert(fibsSquares.front == 1); - fibsSquares.popFront(); - fibsSquares.popFront(); - assert(fibsSquares.front == 4); - fibsSquares.popFront(); - assert(fibsSquares.front == 9); - - auto repeatMap = map!"a"(repeat(1)); - static assert(isInfinite!(typeof(repeatMap))); - - auto intRange = map!"a"([1,2,3]); - static assert(isRandomAccessRange!(typeof(intRange))); - - foreach (DummyType; AllDummyRanges) - { - DummyType d; - auto m = map!"a * a"(d); - - static assert(propagatesRangeType!(typeof(m), DummyType)); - assert(equal(m, [1,4,9,16,25,36,49,64,81,100])); - } - - //Test string access - string s1 = "hello world!"; - dstring s2 = "日本語"; - dstring s3 = "hello world!"d; - auto ms1 = map!(std.ascii.toUpper)(s1); - auto ms2 = map!(std.ascii.toUpper)(s2); - auto ms3 = map!(std.ascii.toUpper)(s3); - static assert(!is(ms1[0])); //narrow strings can't be indexed - assert(ms2[0] == '日'); - assert(ms3[0] == 'H'); - static assert(!is(ms1[0..1])); //narrow strings can't be sliced - assert(equal(ms2[0..2], "日本"w)); - assert(equal(ms3[0..2], "HE")); - - // Issue 5753 - static void voidFun(int) {} - static int nonvoidFun(int) { return 0; } - static assert(!__traits(compiles, map!voidFun([1]))); - static assert(!__traits(compiles, map!(voidFun, voidFun)([1]))); - static assert(!__traits(compiles, map!(nonvoidFun, voidFun)([1]))); - static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1]))); -} -unittest -{ - auto LL = iota(1L, 4L); - auto m = map!"a*a"(LL); - assert(equal(m, [1L, 4L, 9L])); -} - -unittest -{ - // Issue #10130 - map of iota with const step. - const step = 2; - static assert(__traits(compiles, map!(i => i)(iota(0, 10, step)))); - - // Need these to all by const to repro the float case, due to the - // CommonType template used in the float specialization of iota. - const floatBegin = 0.0; - const floatEnd = 1.0; - const floatStep = 0.02; - static assert(__traits(compiles, map!(i => i)(iota(floatBegin, floatEnd, floatStep)))); -} - -unittest -{ - //slicing infinites - auto rr = iota(0, 5).cycle().map!"a * a"(); - alias RR = typeof(rr); - static assert(hasSlicing!RR); - rr = rr[6 .. $]; //Advances 1 cycle and 1 unit - assert(equal(rr[0 .. 5], [1, 4, 9, 16, 0])); -} - -unittest -{ - struct S {int* p;} - auto m = immutable(S).init.repeat().map!"a".save; -} - -/++ -Implements the homonym function (also known as $(D accumulate), $(D -compress), $(D inject), or $(D foldl)) present in various programming -languages of functional flavor. The call $(D reduce!(fun)(seed, -range)) first assigns $(D seed) to an internal variable $(D result), -also called the accumulator. Then, for each element $(D x) in $(D -range), $(D result = fun(result, x)) gets evaluated. Finally, $(D -result) is returned. The one-argument version $(D reduce!(fun)(range)) -works similarly, but it uses the first element of the range as the -seed (the range must be non-empty). - -See also: $(LREF sum) is similar to $(D reduce!((a, b) => a + b)) that offers -precise summing of floating point numbers. -+/ -template reduce(fun...) if (fun.length >= 1) -{ - alias binfuns = staticMap!(binaryFun, fun); - static if (fun.length > 1) - import std.typecons : tuple, isTuple; - - /++ - No-seed version. The first element of $(D r) is used as the seed's value. - - For each function $(D f) in $(D fun), the corresponding - seed type $(D S) is $(D Unqual!(typeof(f(e, e)))), where $(D e) is an - element of $(D r): $(D ElementType!R) for ranges, - and $(D ForeachType!R) otherwise. - - Once S has been determined, then $(D S s = e;) and $(D s = f(s, e);) - must both be legal. - - If $(D r) is empty, an $(D Exception) is thrown. - +/ - auto reduce(R)(R r) - if (isIterable!R) - { - import std.exception : enforce; - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); - alias Args = staticMap!(ReduceSeedType!E, binfuns); - - static if (isInputRange!R) - { - enforce(!r.empty); - Args result = r.front; - r.popFront(); - return reduceImpl!false(r, result); - } - else - { - auto result = Args.init; - return reduceImpl!true(r, result); - } - } - - /++ - Seed version. The seed should be a single value if $(D fun) is a - single function. If $(D fun) is multiple functions, then $(D seed) - should be a $(XREF typecons,Tuple), with one field per function in $(D f). - - For convenience, if the seed is const, or has qualified fields, then - $(D reduce) will operate on an unqualified copy. If this happens - then the returned type will not perfectly match $(D S). - +/ - auto reduce(S, R)(S seed, R r) - if (isIterable!R) - { - static if (fun.length == 1) - return reducePreImpl(r, seed); - else - { - static assert(isTuple!S, algoFormat("Seed %s should be a Tuple", S.stringof)); - return reducePreImpl(r, seed.expand); - } - } - - private auto reducePreImpl(R, Args...)(R r, ref Args args) - { - alias Result = staticMap!(Unqual, Args); - static if (is(Result == Args)) - alias result = args; - else - Result result = args; - return reduceImpl!false(r, result); - } - - private auto reduceImpl(bool mustInitialize, R, Args...)(R r, ref Args args) - if (isIterable!R) - { - static assert(Args.length == fun.length, - algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length)); - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); - - static if (mustInitialize) bool initialized = false; - foreach (/+auto ref+/ E e; r) // @@@4707@@@ - { - foreach (i, f; binfuns) - static assert(is(typeof(args[i] = f(args[i], e))), - algoFormat("Incompatible function/seed/element: %s/%s/%s", fullyQualifiedName!f, Args[i].stringof, E.stringof)); - - static if (mustInitialize) if (initialized == false) - { - import std.conv : emplaceRef; - foreach (i, f; binfuns) - emplaceRef!(Args[i])(args[i], e); - initialized = true; - continue; - } - - foreach (i, f; binfuns) - args[i] = f(args[i], e); - } - static if (mustInitialize) if (!initialized) throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value."); - - static if (Args.length == 1) - return args[0]; - else - return tuple(args); - } -} - -//Helper for Reduce -private template ReduceSeedType(E) -{ - static template ReduceSeedType(alias fun) - { - E e = E.init; - static alias ReduceSeedType = Unqual!(typeof(fun(e, e))); - - //Check the Seed type is useable. - ReduceSeedType s = ReduceSeedType.init; - static assert(is(typeof({ReduceSeedType s = e;})) && is(typeof(s = fun(s, e))), - algoFormat("Unable to deduce an acceptable seed type for %s with element type %s.", fullyQualifiedName!fun, E.stringof)); - } -} - -/** -Many aggregate range operations turn out to be solved with $(D reduce) -quickly and easily. The example below illustrates $(D reduce)'s -remarkable power and flexibility. -*/ -unittest -{ - import std.math : approxEqual; - - int[] arr = [ 1, 2, 3, 4, 5 ]; - // Sum all elements - auto sum = reduce!((a,b) => a + b)(0, arr); - assert(sum == 15); - - // Sum again, using a string predicate with "a" and "b" - sum = reduce!"a + b"(0, arr); - assert(sum == 15); - - // Compute the maximum of all elements - auto largest = reduce!(max)(arr); - assert(largest == 5); - - // Max again, but with Uniform Function Call Syntax (UFCS) - largest = arr.reduce!(max); - assert(largest == 5); - - // Compute the number of odd elements - auto odds = reduce!((a,b) => a + (b & 1))(0, arr); - assert(odds == 3); - - // Compute the sum of squares - auto ssquares = reduce!((a,b) => a + b * b)(0, arr); - assert(ssquares == 55); - - // Chain multiple ranges into seed - int[] a = [ 3, 4 ]; - int[] b = [ 100 ]; - auto r = reduce!("a + b")(chain(a, b)); - assert(r == 107); - - // Mixing convertible types is fair game, too - double[] c = [ 2.5, 3.0 ]; - auto r1 = reduce!("a + b")(chain(a, b, c)); - assert(approxEqual(r1, 112.5)); - - // To minimize nesting of parentheses, Uniform Function Call Syntax can be used - auto r2 = chain(a, b, c).reduce!("a + b"); - assert(approxEqual(r2, 112.5)); -} - -/** -Sometimes it is very useful to compute multiple aggregates in one pass. -One advantage is that the computation is faster because the looping overhead -is shared. That's why $(D reduce) accepts multiple functions. -If two or more functions are passed, $(D reduce) returns a -$(XREF typecons, Tuple) object with one member per passed-in function. -The number of seeds must be correspondingly increased. -*/ -unittest -{ - import std.math : approxEqual, sqrt; - - double[] a = [ 3.0, 4, 7, 11, 3, 2, 5 ]; - // Compute minimum and maximum in one pass - auto r = reduce!(min, max)(a); - // The type of r is Tuple!(int, int) - assert(approxEqual(r[0], 2)); // minimum - assert(approxEqual(r[1], 11)); // maximum - - // Compute sum and sum of squares in one pass - r = reduce!("a + b", "a + b * b")(tuple(0.0, 0.0), a); - assert(approxEqual(r[0], 35)); // sum - assert(approxEqual(r[1], 233)); // sum of squares - // Compute average and standard deviation from the above - auto avg = r[0] / a.length; - auto stdev = sqrt(r[1] / a.length - avg * avg); -} - -unittest -{ - import std.exception : assertThrown; - - double[] a = [ 3, 4 ]; - auto r = reduce!("a + b")(0.0, a); - assert(r == 7); - r = reduce!("a + b")(a); - assert(r == 7); - r = reduce!(min)(a); - assert(r == 3); - double[] b = [ 100 ]; - auto r1 = reduce!("a + b")(chain(a, b)); - assert(r1 == 107); - - // two funs - auto r2 = reduce!("a + b", "a - b")(tuple(0.0, 0.0), a); - assert(r2[0] == 7 && r2[1] == -7); - auto r3 = reduce!("a + b", "a - b")(a); - assert(r3[0] == 7 && r3[1] == -1); - - a = [ 1, 2, 3, 4, 5 ]; - // Stringize with commas - string rep = reduce!("a ~ `, ` ~ to!(string)(b)")("", a); - assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]"); - - // Test the opApply case. - static struct OpApply - { - bool actEmpty; - - int opApply(int delegate(ref int) dg) - { - int res; - if (actEmpty) return res; - - foreach (i; 0..100) - { - res = dg(i); - if (res) break; - } - return res; - } - } - - OpApply oa; - auto hundredSum = reduce!"a + b"(iota(100)); - assert(reduce!"a + b"(5, oa) == hundredSum + 5); - assert(reduce!"a + b"(oa) == hundredSum); - assert(reduce!("a + b", max)(oa) == tuple(hundredSum, 99)); - assert(reduce!("a + b", max)(tuple(5, 0), oa) == tuple(hundredSum + 5, 99)); - - // Test for throwing on empty range plus no seed. - assertThrown(reduce!"a + b"([1, 2][0..0])); - - oa.actEmpty = true; - assertThrown(reduce!"a + b"(oa)); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - const float a = 0.0; - const float[] b = [ 1.2, 3, 3.3 ]; - float[] c = [ 1.2, 3, 3.3 ]; - auto r = reduce!"a + b"(a, b); - r = reduce!"a + b"(a, c); -} - -unittest -{ - // Issue #10408 - Two-function reduce of a const array. - const numbers = [10, 30, 20]; - immutable m = reduce!(min)(numbers); - assert(m == 10); - immutable minmax = reduce!(min, max)(numbers); - assert(minmax == tuple(10, 30)); -} - -unittest -{ - //10709 - enum foo = "a + 0.5 * b"; - auto r = [0, 1, 2, 3]; - auto r1 = reduce!foo(r); - auto r2 = reduce!(foo, foo)(r); - assert(r1 == 3); - assert(r2 == tuple(3, 3)); -} - -unittest -{ - int i = 0; - static struct OpApply - { - int opApply(int delegate(ref int) dg) - { - int[] a = [1, 2, 3]; - - int res = 0; - foreach (ref e; a) - { - res = dg(e); - if (res) break; - } - return res; - } - } - //test CTFE and functions with context - int fun(int a, int b){return a + b + 1;} - auto foo() - { - auto a = reduce!(fun)([1, 2, 3]); - auto b = reduce!(fun, fun)([1, 2, 3]); - auto c = reduce!(fun)(0, [1, 2, 3]); - auto d = reduce!(fun, fun)(tuple(0, 0), [1, 2, 3]); - auto e = reduce!(fun)(0, OpApply()); - auto f = reduce!(fun, fun)(tuple(0, 0), OpApply()); - - return max(a, b.expand, c, d.expand); - } - auto a = foo(); - enum b = foo(); -} - -unittest -{ - //http://forum.dlang.org/thread/oghtttkopzjshsuflelk@forum.dlang.org - //Seed is tuple of const. - static auto minmaxElement(alias F = min, alias G = max, R)(in R range) - @safe pure nothrow if (isInputRange!R) - { - return reduce!(F, G)(tuple(ElementType!R.max, - ElementType!R.min), range); - } - assert(minmaxElement([1, 2, 3])== tuple(1, 3)); -} - -unittest //12569 -{ - import std.typecons: tuple; - dchar c = 'a'; - reduce!(min, max)(tuple(c, c), "hello"); // OK - static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello")))); - static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello")))); - - - //"Seed dchar should be a Tuple" - static assert(!is(typeof(reduce!(min, max)(c, "hello")))); - //"Seed (dchar) does not have the correct amount of fields (should be 2)" - static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello")))); - //"Seed (dchar, dchar, dchar) does not have the correct amount of fields (should be 2)" - static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello")))); - //"Incompatable function/seed/element: all(alias pred = "a")/int/dchar" - static assert(!is(typeof(reduce!all(1, "hello")))); - static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello")))); -} - -unittest //13304 -{ - int[] data; - static assert(is(typeof(reduce!((a, b)=>a+b)(data)))); -} - -// sum -/** -Sums elements of $(D r), which must be a finite input range. Although -conceptually $(D sum(r)) is equivalent to $(D reduce!((a, b) => a + -b)(0, r)), $(D sum) uses specialized algorithms to maximize accuracy, -as follows. - -$(UL -$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a -random-access range with length and slicing, then $(D sum) uses the -$(WEB en.wikipedia.org/wiki/Pairwise_summation, pairwise summation) -algorithm.) -$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a -finite input range (but not a random-access range with slicing), then -$(D sum) uses the $(WEB en.wikipedia.org/wiki/Kahan_summation, -Kahan summation) algorithm.) -$(LI In all other cases, a simple element by element addition is done.) -) - -For floating point inputs, calculations are made in $(D real) -precision for $(D real) inputs and in $(D double) precision otherwise -(Note this is a special case that deviates from $(D reduce)'s behavior, -which would have kept $(D float) precision for a $(D float) range). -For all other types, the calculations are done in the same type obtained -from from adding two elements of the range, which may be a different -type from the elements themselves (for example, in case of integral promotion). - -A seed may be passed to $(D sum). Not only will this seed be used as an initial -value, but its type will override all the above, and determine the algorithm -and precision used for sumation. - -Note that these specialized summing algorithms execute more primitive operations -than vanilla summation. Therefore, if in certain cases maximum speed is required -at expense of precision, one can use $(D reduce!((a, b) => a + b)(0, r)), which -is not specialized for summation. - */ -auto sum(R)(R r) -if (isInputRange!R && !isInfinite!R && is(typeof(r.front + r.front))) -{ - alias E = Unqual!(ElementType!R); - static if (isFloatingPoint!E) - alias Seed = typeof(E.init + 0.0); //biggest of double/real - else - alias Seed = typeof(r.front + r.front); - return sum(r, Unqual!Seed(0)); -} -/// ditto -auto sum(R, E)(R r, E seed) -if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front))) -{ - static if (isFloatingPoint!E) - { - static if (hasLength!R && hasSlicing!R) - { - import std.internal.math.summation : sumPairwise; - return seed + sumPairwise!(R, E)(r); - } - else - { - import std.internal.math.summation : sumKahan; - return sumKahan!(R, E)(r, seed); - } - } - else - { - return reduce!"a + b"(seed, r); - } -} - - -/// Ditto -@safe pure nothrow unittest -{ - //simple integral sumation - assert(sum([ 1, 2, 3, 4]) == 10); - - //with integral promotion - assert(sum([false, true, true, false, true]) == 3); - assert(sum(ubyte.max.repeat(100)) == 25500); - - //The result may overflow - assert(uint.max.repeat(3).sum() == 4294967293U ); - //But a seed can be used to change the sumation primitive - assert(uint.max.repeat(3).sum(ulong.init) == 12884901885UL); - - //Floating point sumation - assert(sum([1.0, 2.0, 3.0, 4.0]) == 10); - - //Floating point operations have double precision minimum - static assert(is(typeof(sum([1F, 2F, 3F, 4F])) == double)); - assert(sum([1F, 2, 3, 4]) == 10); - - //Force pair-wise floating point sumation on large integers - import std.math : approxEqual; - assert(iota(ulong.max / 2, ulong.max / 2 + 4096).sum(0.0) - .approxEqual((ulong.max / 2) * 4096.0 + 4096^^2 / 2)); -} - -@safe pure nothrow unittest -{ - static assert(is(typeof(sum([cast( byte)1])) == int)); - static assert(is(typeof(sum([cast(ubyte)1])) == int)); - static assert(is(typeof(sum([ 1, 2, 3, 4])) == int)); - static assert(is(typeof(sum([ 1U, 2U, 3U, 4U])) == uint)); - static assert(is(typeof(sum([ 1L, 2L, 3L, 4L])) == long)); - static assert(is(typeof(sum([1UL, 2UL, 3UL, 4UL])) == ulong)); - - int[] empty; - assert(sum(empty) == 0); - assert(sum([42]) == 42); - assert(sum([42, 43]) == 42 + 43); - assert(sum([42, 43, 44]) == 42 + 43 + 44); - assert(sum([42, 43, 44, 45]) == 42 + 43 + 44 + 45); -} - -@safe pure nothrow unittest -{ - static assert(is(typeof(sum([1.0, 2.0, 3.0, 4.0])) == double)); - static assert(is(typeof(sum([ 1F, 2F, 3F, 4F])) == double)); - const(float[]) a = [1F, 2F, 3F, 4F]; - static assert(is(typeof(sum(a)) == double)); - const(float)[] b = [1F, 2F, 3F, 4F]; - static assert(is(typeof(sum(a)) == double)); - - double[] empty; - assert(sum(empty) == 0); - assert(sum([42.]) == 42); - assert(sum([42., 43.]) == 42 + 43); - assert(sum([42., 43., 44.]) == 42 + 43 + 44); - assert(sum([42., 43., 44., 45.5]) == 42 + 43 + 44 + 45.5); -} - -@safe pure nothrow unittest -{ - import std.container; - static assert(is(typeof(sum(SList!float()[])) == double)); - static assert(is(typeof(sum(SList!double()[])) == double)); - static assert(is(typeof(sum(SList!real()[])) == real)); - - assert(sum(SList!double()[]) == 0); - assert(sum(SList!double(1)[]) == 1); - assert(sum(SList!double(1, 2)[]) == 1 + 2); - assert(sum(SList!double(1, 2, 3)[]) == 1 + 2 + 3); - assert(sum(SList!double(1, 2, 3, 4)[]) == 10); -} - -@safe pure nothrow unittest // 12434 -{ - immutable a = [10, 20]; - auto s1 = sum(a); // Error - auto s2 = a.map!(x => x).sum; // Error -} - -unittest -{ - import std.bigint; - immutable BigInt[] a = BigInt("1_000_000_000_000_000_000").repeat(10).array(); - immutable ulong[] b = (ulong.max/2).repeat(10).array(); - auto sa = a.sum(); - auto sb = b.sum(BigInt(0)); //reduce ulongs into bigint - assert(sa == BigInt("10_000_000_000_000_000_000")); - assert(sb == (BigInt(ulong.max/2) * 10)); -} - -/** -Fills $(D range) with a $(D filler). - */ -void fill(Range, Value)(Range range, Value filler) - if (isInputRange!Range && is(typeof(range.front = filler))) -{ - alias T = ElementType!Range; - - static if (is(typeof(range[] = filler))) - { - range[] = filler; - } - else static if (is(typeof(range[] = T(filler)))) - { - range[] = T(filler); - } - else - { - for ( ; !range.empty; range.popFront() ) - { - range.front = filler; - } - } -} - -/// -unittest -{ - int[] a = [ 1, 2, 3, 4 ]; - fill(a, 5); - assert(a == [ 5, 5, 5, 5 ]); -} - -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = [ 1, 2, 3 ]; - fill(a, 6); - assert(a == [ 6, 6, 6 ], text(a)); - - void fun0() - { - foreach (i; 0 .. 1000) - { - foreach (ref e; a) e = 6; - } - } - void fun1() { foreach (i; 0 .. 1000) fill(a, 6); } - //void fun2() { foreach (i; 0 .. 1000) fill2(a, 6); } - //writeln(benchmark!(fun0, fun1, fun2)(10000)); - - // fill should accept InputRange - alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); - enum filler = uint.max; - InputRange range; - fill(range, filler); - foreach (value; range.arr) - assert(value == filler); -} - -unittest -{ - //ER8638_1 IS_NOT self assignable - static struct ER8638_1 - { - void opAssign(int){} - } - - //ER8638_1 IS self assignable - static struct ER8638_2 - { - void opAssign(ER8638_2){} - void opAssign(int){} - } - - auto er8638_1 = new ER8638_1[](10); - auto er8638_2 = new ER8638_2[](10); - er8638_1.fill(5); //generic case - er8638_2.fill(5); //opSlice(T.init) case -} - -unittest -{ - { - int[] a = [1, 2, 3]; - immutable(int) b = 0; - static assert(__traits(compiles, a.fill(b))); - } - { - double[] a = [1, 2, 3]; - immutable(int) b = 0; - static assert(__traits(compiles, a.fill(b))); - } -} - -/** -Fills $(D range) with a pattern copied from $(D filler). The length of -$(D range) does not have to be a multiple of the length of $(D -filler). If $(D filler) is empty, an exception is thrown. - */ -void fill(Range1, Range2)(Range1 range, Range2 filler) - if (isInputRange!Range1 - && (isForwardRange!Range2 - || (isInputRange!Range2 && isInfinite!Range2)) - && is(typeof(Range1.init.front = Range2.init.front))) -{ - static if (isInfinite!Range2) - { - //Range2 is infinite, no need for bounds checking or saving - static if (hasSlicing!Range2 && hasLength!Range1 - && is(typeof(filler[0 .. range.length]))) - { - copy(filler[0 .. range.length], range); - } - else - { - //manual feed - for ( ; !range.empty; range.popFront(), filler.popFront()) - { - range.front = filler.front; - } - } - } - else - { - import std.exception : enforce; - - enforce(!filler.empty, "Cannot fill range with an empty filler"); - - static if (hasLength!Range1 && hasLength!Range2 - && is(typeof(range.length > filler.length))) - { - //Case we have access to length - auto len = filler.length; - //Start by bulk copies - while (range.length > len) - { - range = copy(filler.save, range); - } - - //and finally fill the partial range. No need to save here. - static if (hasSlicing!Range2 && is(typeof(filler[0 .. range.length]))) - { - //use a quick copy - auto len2 = range.length; - range = copy(filler[0 .. len2], range); - } - else - { - //iterate. No need to check filler, it's length is longer than range's - for (; !range.empty; range.popFront(), filler.popFront()) - { - range.front = filler.front; - } - } - } - else - { - //Most basic case. - auto bck = filler.save; - for (; !range.empty; range.popFront(), filler.popFront()) - { - if (filler.empty) filler = bck.save; - range.front = filler.front; - } - } - } -} - -/// -unittest -{ - int[] a = [ 1, 2, 3, 4, 5 ]; - int[] b = [ 8, 9 ]; - fill(a, b); - assert(a == [ 8, 9, 8, 9, 8 ]); -} - -unittest -{ - import std.exception : assertThrown; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = [ 1, 2, 3, 4, 5 ]; - int[] b = [1, 2]; - fill(a, b); - assert(a == [ 1, 2, 1, 2, 1 ]); - - // fill should accept InputRange - alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); - InputRange range; - fill(range,[1,2]); - foreach (i,value;range.arr) - assert(value == (i%2==0?1:2)); - - //test with a input being a "reference forward" range - fill(a, new ReferenceForwardRange!int([8, 9])); - assert(a == [8, 9, 8, 9, 8]); - - //test with a input being an "infinite input" range - fill(a, new ReferenceInfiniteInputRange!int()); - assert(a == [0, 1, 2, 3, 4]); - - //empty filler test - assertThrown(fill(a, a[$..$])); -} - -/** -Fills a range with a value. Assumes that the range does not currently -contain meaningful content. This is of interest for structs that -define copy constructors (for all other types, fill and -uninitializedFill are equivalent). - -uninitializedFill will only operate on ranges that expose references to its -members and have assignable elements. - -Example: ----- -struct S { ... } -S[] s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5]; -uninitializedFill(s, 42); -assert(s == [ 42, 42, 42, 42, 42 ]); ----- - */ -void uninitializedFill(Range, Value)(Range range, Value filler) - if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = filler))) -{ - alias T = ElementType!Range; - static if (hasElaborateAssign!T) - { - import std.conv : emplaceRef; - - // Must construct stuff by the book - for (; !range.empty; range.popFront()) - emplaceRef!T(range.front, filler); - } - else - // Doesn't matter whether fill is initialized or not - return fill(range, filler); -} - -/** -Initializes all elements of a range with their $(D .init) -value. Assumes that the range does not currently contain meaningful -content. - -initializeAll will operate on ranges that expose references to its -members and have assignable elements, as well as on (mutable) strings. - -Example: ----- -struct S { ... } -S[] s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5]; -initializeAll(s); -assert(s == [ 0, 0, 0, 0, 0 ]); ----- - */ -void initializeAll(Range)(Range range) - if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range) -{ - import core.stdc.string : memset, memcpy; - - alias T = ElementType!Range; - static if (hasElaborateAssign!T) - { - //Elaborate opAssign. Must go the memcpy road. - //We avoid calling emplace here, because our goal is to initialize to - //the static state of T.init, - //So we want to avoid any un-necassarilly CC'ing of T.init - auto p = typeid(T).init().ptr; - if (p) - for ( ; !range.empty ; range.popFront() ) - memcpy(addressOf(range.front), p, T.sizeof); - else - static if (isDynamicArray!Range) - memset(range.ptr, 0, range.length * T.sizeof); - else - for ( ; !range.empty ; range.popFront() ) - memset(addressOf(range.front), 0, T.sizeof); - } - else - fill(range, T.init); -} - -// ditto -void initializeAll(Range)(Range range) - if (is(Range == char[]) || is(Range == wchar[])) -{ - alias T = ElementEncodingType!Range; - range[] = T.init; -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - //Test strings: - //Must work on narrow strings. - //Must reject const - char[3] a = void; - a[].initializeAll(); - assert(a[] == [char.init, char.init, char.init]); - string s; - assert(!__traits(compiles, s.initializeAll())); - - //Note: Cannot call uninitializedFill on narrow strings - - enum e {e1, e2} - e[3] b1 = void; - b1[].initializeAll(); - assert(b1[] == [e.e1, e.e1, e.e1]); - e[3] b2 = void; - b2[].uninitializedFill(e.e2); - assert(b2[] == [e.e2, e.e2, e.e2]); - - static struct S1 - { - int i; - } - static struct S2 - { - int i = 1; - } - static struct S3 - { - int i; - this(this){} - } - static struct S4 - { - int i = 1; - this(this){} - } - static assert (!hasElaborateAssign!S1); - static assert (!hasElaborateAssign!S2); - static assert ( hasElaborateAssign!S3); - static assert ( hasElaborateAssign!S4); - assert (!typeid(S1).init().ptr); - assert ( typeid(S2).init().ptr); - assert (!typeid(S3).init().ptr); - assert ( typeid(S4).init().ptr); - - foreach(S; TypeTuple!(S1, S2, S3, S4)) - { - //initializeAll - { - //Array - S[3] ss1 = void; - ss1[].initializeAll(); - assert(ss1[] == [S.init, S.init, S.init]); - - //Not array - S[3] ss2 = void; - auto sf = ss2[].filter!"true"(); - - sf.initializeAll(); - assert(ss2[] == [S.init, S.init, S.init]); - } - //uninitializedFill - { - //Array - S[3] ss1 = void; - ss1[].uninitializedFill(S(2)); - assert(ss1[] == [S(2), S(2), S(2)]); - - //Not array - S[3] ss2 = void; - auto sf = ss2[].filter!"true"(); - sf.uninitializedFill(S(2)); - assert(ss2[] == [S(2), S(2), S(2)]); - } - } -} - -/** -$(D auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range));) - -Implements the homonym function present in various programming -languages of functional flavor. The call $(D filter!(predicate)(range)) -returns a new range only containing elements $(D x) in $(D range) for -which $(D predicate(x)) is $(D true). - */ -template filter(alias pred) if (is(typeof(unaryFun!pred))) -{ - auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range)) - { - return FilterResult!(unaryFun!pred, Range)(rs); - } -} - -/// -unittest -{ - import std.math : approxEqual; - - int[] arr = [ 1, 2, 3, 4, 5 ]; - - // Sum all elements - auto small = filter!(a => a < 3)(arr); - assert(equal(small, [ 1, 2 ])); - - // Sum again, but with Uniform Function Call Syntax (UFCS) - auto sum = arr.filter!(a => a < 3); - assert(equal(sum, [ 1, 2 ])); - - // In combination with chain() to span multiple ranges - int[] a = [ 3, -2, 400 ]; - int[] b = [ 100, -101, 102 ]; - auto r = chain(a, b).filter!(a => a > 0); - assert(equal(r, [ 3, 400, 100, 102 ])); - - // Mixing convertible types is fair game, too - double[] c = [ 2.5, 3.0 ]; - auto r1 = chain(c, a, b).filter!(a => cast(int) a != a); - assert(approxEqual(r1, [ 2.5 ])); -} - -private struct FilterResult(alias pred, Range) -{ - alias R = Unqual!Range; - R _input; - - this(R r) - { - _input = r; - while (!_input.empty && !pred(_input.front)) - { - _input.popFront(); - } - } - - auto opSlice() { return this; } - - static if (isInfinite!Range) - { - enum bool empty = false; - } - else - { - @property bool empty() { return _input.empty; } - } - - void popFront() - { - do - { - _input.popFront(); - } while (!_input.empty && !pred(_input.front)); - } - - @property auto ref front() - { - return _input.front; - } - - static if (isForwardRange!R) - { - @property auto save() - { - return typeof(this)(_input.save); - } - } -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 3, 4, 2 ]; - auto r = filter!("a > 3")(a); - static assert(isForwardRange!(typeof(r))); - assert(equal(r, [ 4 ][])); - - a = [ 1, 22, 3, 42, 5 ]; - auto under10 = filter!("a < 10")(a); - assert(equal(under10, [1, 3, 5][])); - static assert(isForwardRange!(typeof(under10))); - under10.front = 4; - assert(equal(under10, [4, 3, 5][])); - under10.front = 40; - assert(equal(under10, [40, 3, 5][])); - under10.front = 1; - - auto infinite = filter!"a > 2"(repeat(3)); - static assert(isInfinite!(typeof(infinite))); - static assert(isForwardRange!(typeof(infinite))); - - foreach (DummyType; AllDummyRanges) { - DummyType d; - auto f = filter!"a & 1"(d); - assert(equal(f, [1,3,5,7,9])); - - static if (isForwardRange!DummyType) { - static assert(isForwardRange!(typeof(f))); - } - } - - // With delegates - int x = 10; - int overX(int a) { return a > x; } - typeof(filter!overX(a)) getFilter() - { - return filter!overX(a); - } - auto r1 = getFilter(); - assert(equal(r1, [22, 42])); - - // With chain - auto nums = [0,1,2,3,4]; - assert(equal(filter!overX(chain(a, nums)), [22, 42])); - - // With copying of inner struct Filter to Map - auto arr = [1,2,3,4,5]; - auto m = map!"a + 1"(filter!"a < 4"(arr)); -} - -unittest -{ - int[] a = [ 3, 4 ]; - const aConst = a; - auto r = filter!("a > 3")(aConst); - assert(equal(r, [ 4 ][])); - - a = [ 1, 22, 3, 42, 5 ]; - auto under10 = filter!("a < 10")(a); - assert(equal(under10, [1, 3, 5][])); - assert(equal(under10.save, [1, 3, 5][])); - assert(equal(under10.save, under10)); - - // With copying of inner struct Filter to Map - auto arr = [1,2,3,4,5]; - auto m = map!"a + 1"(filter!"a < 4"(arr)); -} - -unittest -{ - import std.functional : compose, pipe; - - assert(equal(compose!(map!"2 * a", filter!"a & 1")([1,2,3,4,5]), - [2,6,10])); - assert(equal(pipe!(filter!"a & 1", map!"2 * a")([1,2,3,4,5]), - [2,6,10])); -} - -unittest -{ - int x = 10; - int underX(int a) { return a < x; } - const(int)[] list = [ 1, 2, 10, 11, 3, 4 ]; - assert(equal(filter!underX(list), [ 1, 2, 3, 4 ])); -} - -/** - * $(D auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range));) - * - * Similar to $(D filter), except it defines a bidirectional - * range. There is a speed disadvantage - the constructor spends time - * finding the last element in the range that satisfies the filtering - * condition (in addition to finding the first one). The advantage is - * that the filtered range can be spanned from both directions. Also, - * $(XREF range, retro) can be applied against the filtered range. - * - */ -template filterBidirectional(alias pred) -{ - auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range)) - { - return FilterBidiResult!(unaryFun!pred, Range)(r); - } -} - -/// -unittest -{ - int[] arr = [ 1, 2, 3, 4, 5 ]; - auto small = filterBidirectional!("a < 3")(arr); - static assert(isBidirectionalRange!(typeof(small))); - assert(small.back == 2); - assert(equal(small, [ 1, 2 ])); - assert(equal(retro(small), [ 2, 1 ])); - // In combination with chain() to span multiple ranges - int[] a = [ 3, -2, 400 ]; - int[] b = [ 100, -101, 102 ]; - auto r = filterBidirectional!("a > 0")(chain(a, b)); - assert(r.back == 102); -} - -private struct FilterBidiResult(alias pred, Range) -{ - alias R = Unqual!Range; - R _input; - - this(R r) - { - _input = r; - while (!_input.empty && !pred(_input.front)) _input.popFront(); - while (!_input.empty && !pred(_input.back)) _input.popBack(); - } - - @property bool empty() { return _input.empty; } - - void popFront() - { - do - { - _input.popFront(); - } while (!_input.empty && !pred(_input.front)); - } - - @property auto ref front() - { - return _input.front; - } - - void popBack() - { - do - { - _input.popBack(); - } while (!_input.empty && !pred(_input.back)); - } - - @property auto ref back() - { - return _input.back; - } - - @property auto save() - { - return typeof(this)(_input.save); - } -} - -// move -/** -Moves $(D source) into $(D target) via a destructive -copy. -*/ -void move(T)(ref T source, ref T target) -{ - import core.stdc.string : memcpy; - - static if (hasAliasing!T) if (!__ctfe) - { - import std.exception : doesPointTo; - assert(!doesPointTo(source, source), "Cannot move object with internal pointer."); - } - - static if (is(T == struct)) - { - if (&source == &target) return; - // Most complicated case. Destroy whatever target had in it - // and bitblast source over it - static if (hasElaborateDestructor!T) typeid(T).destroy(&target); - - static if (hasElaborateAssign!T || !isAssignable!T) - memcpy(&target, &source, T.sizeof); - else - target = source; - - // If the source defines a destructor or a postblit hook, we must obliterate the - // object in order to avoid double freeing and undue aliasing - static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) - { - static T empty; - static if (T.tupleof.length > 0 && - T.tupleof[$-1].stringof.endsWith("this")) - { - // If T is nested struct, keep original context pointer - memcpy(&source, &empty, T.sizeof - (void*).sizeof); - } - else - { - memcpy(&source, &empty, T.sizeof); - } - } - } - else - { - // Primitive data (including pointers and arrays) or class - - // assignment works great - target = source; - } -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - import std.exception : assertCTFEable; - assertCTFEable!((){ - Object obj1 = new Object; - Object obj2 = obj1; - Object obj3; - move(obj2, obj3); - assert(obj3 is obj1); - - static struct S1 { int a = 1, b = 2; } - S1 s11 = { 10, 11 }; - S1 s12; - move(s11, s12); - assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); - - static struct S2 { int a = 1; int * b; } - S2 s21 = { 10, null }; - s21.b = new int; - S2 s22; - move(s21, s22); - assert(s21 == s22); - }); - // Issue 5661 test(1) - static struct S3 - { - static struct X { int n = 0; ~this(){n = 0;} } - X x; - } - static assert(hasElaborateDestructor!S3); - S3 s31, s32; - s31.x.n = 1; - move(s31, s32); - assert(s31.x.n == 0); - assert(s32.x.n == 1); - - // Issue 5661 test(2) - static struct S4 - { - static struct X { int n = 0; this(this){n = 0;} } - X x; - } - static assert(hasElaborateCopyConstructor!S4); - S4 s41, s42; - s41.x.n = 1; - move(s41, s42); - assert(s41.x.n == 0); - assert(s42.x.n == 1); -} - -/// Ditto -T move(T)(ref T source) -{ - import core.stdc.string : memcpy; - - static if (hasAliasing!T) if (!__ctfe) - { - import std.exception : doesPointTo; - assert(!doesPointTo(source, source), "Cannot move object with internal pointer."); - } - - T result = void; - static if (is(T == struct)) - { - // Can avoid destructing result. - static if (hasElaborateAssign!T || !isAssignable!T) - memcpy(&result, &source, T.sizeof); - else - result = source; - - // If the source defines a destructor or a postblit hook, we must obliterate the - // object in order to avoid double freeing and undue aliasing - static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) - { - static T empty; - static if (T.tupleof.length > 0 && - T.tupleof[$-1].stringof.endsWith("this")) - { - // If T is nested struct, keep original context pointer - memcpy(&source, &empty, T.sizeof - (void*).sizeof); - } - else - { - memcpy(&source, &empty, T.sizeof); - } - } - } - else - { - // Primitive data (including pointers and arrays) or class - - // assignment works great - result = source; - } - return result; -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - import std.exception : assertCTFEable; - assertCTFEable!((){ - Object obj1 = new Object; - Object obj2 = obj1; - Object obj3 = move(obj2); - assert(obj3 is obj1); - - static struct S1 { int a = 1, b = 2; } - S1 s11 = { 10, 11 }; - S1 s12 = move(s11); - assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); - - static struct S2 { int a = 1; int * b; } - S2 s21 = { 10, null }; - s21.b = new int; - S2 s22 = move(s21); - assert(s21 == s22); - }); - - // Issue 5661 test(1) - static struct S3 - { - static struct X { int n = 0; ~this(){n = 0;} } - X x; - } - static assert(hasElaborateDestructor!S3); - S3 s31; - s31.x.n = 1; - S3 s32 = move(s31); - assert(s31.x.n == 0); - assert(s32.x.n == 1); - - // Issue 5661 test(2) - static struct S4 - { - static struct X { int n = 0; this(this){n = 0;} } - X x; - } - static assert(hasElaborateCopyConstructor!S4); - S4 s41; - s41.x.n = 1; - S4 s42 = move(s41); - assert(s41.x.n == 0); - assert(s42.x.n == 1); -} - -unittest//Issue 6217 -{ - auto x = map!"a"([1,2,3]); - x = move(x); -} - -unittest// Issue 8055 -{ - static struct S - { - int x; - ~this() - { - assert(x == 0); - } - } - S foo(S s) - { - return move(s); - } - S a; - a.x = 0; - auto b = foo(a); - assert(b.x == 0); -} - -unittest// Issue 8057 -{ - int n = 10; - struct S - { - int x; - ~this() - { - // Access to enclosing scope - assert(n == 10); - } - } - S foo(S s) - { - // Move nested struct - return move(s); - } - S a; - a.x = 1; - auto b = foo(a); - assert(b.x == 1); - - // Regression 8171 - static struct Array(T) - { - // nested struct has no member - struct Payload - { - ~this() {} - } - } - Array!int.Payload x = void; - static assert(__traits(compiles, move(x) )); - static assert(__traits(compiles, move(x, x) )); -} - -// moveAll -/** -For each element $(D a) in $(D src) and each element $(D b) in $(D -tgt) in lockstep in increasing order, calls $(D move(a, b)). Returns -the leftover portion of $(D tgt). Throws an exception if there is not -enough room in $(D tgt) to accommodate all of $(D src). - -Preconditions: -$(D walkLength(src) <= walkLength(tgt)) - */ -Range2 moveAll(Range1, Range2)(Range1 src, Range2 tgt) -if (isInputRange!Range1 && isInputRange!Range2 - && is(typeof(move(src.front, tgt.front)))) -{ - import std.exception : enforce; - - static if (isRandomAccessRange!Range1 && hasLength!Range1 && hasLength!Range2 - && hasSlicing!Range2 && isRandomAccessRange!Range2) - { - auto toMove = src.length; - enforce(toMove <= tgt.length); // shouldn't this be an assert? - foreach (idx; 0 .. toMove) - move(src[idx], tgt[idx]); - return tgt[toMove .. tgt.length]; - } - else - { - for (; !src.empty; src.popFront(), tgt.popFront()) - { - enforce(!tgt.empty); //ditto? - move(src.front, tgt.front); - } - return tgt; - } -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 1, 2, 3 ]; - int[] b = new int[5]; - assert(moveAll(a, b) is b[3 .. $]); - assert(a == b[0 .. 3]); - assert(a == [ 1, 2, 3 ]); -} - -// moveSome -/** -For each element $(D a) in $(D src) and each element $(D b) in $(D -tgt) in lockstep in increasing order, calls $(D move(a, b)). Stops -when either $(D src) or $(D tgt) have been exhausted. Returns the -leftover portions of the two ranges. - */ -Tuple!(Range1, Range2) moveSome(Range1, Range2)(Range1 src, Range2 tgt) -if (isInputRange!Range1 && isInputRange!Range2 - && is(typeof(move(src.front, tgt.front)))) -{ - import std.exception : enforce; - - for (; !src.empty && !tgt.empty; src.popFront(), tgt.popFront()) - { - enforce(!tgt.empty); - move(src.front, tgt.front); - } - return tuple(src, tgt); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 1, 2, 3, 4, 5 ]; - int[] b = new int[3]; - assert(moveSome(a, b)[0] is a[3 .. $]); - assert(a[0 .. 3] == b); - assert(a == [ 1, 2, 3, 4, 5 ]); -} - -// swap -/** -Swaps $(D lhs) and $(D rhs). The instances $(D lhs) and $(D rhs) are moved in -memory, without ever calling $(D opAssign), nor any other function. $(D T) -need not be assignable at all to be swapped. - -If $(D lhs) and $(D rhs) reference the same instance, then nothing is done. - -$(D lhs) and $(D rhs) must be mutable. If $(D T) is a struct or union, then -its fields must also all be (recursivelly) mutable. -*/ -void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow -if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) -{ - static if (hasAliasing!T) if (!__ctfe) - { - import std.exception : doesPointTo; - assert(!doesPointTo(lhs, lhs), "Swap: lhs internal pointer."); - assert(!doesPointTo(rhs, rhs), "Swap: rhs internal pointer."); - assert(!doesPointTo(lhs, rhs), "Swap: lhs points to rhs."); - assert(!doesPointTo(rhs, lhs), "Swap: rhs points to lhs."); - } - - static if (hasElaborateAssign!T || !isAssignable!T) - { - if (&lhs != &rhs) - { - // For structs with non-trivial assignment, move memory directly - ubyte[T.sizeof] t = void; - auto a = (cast(ubyte*) &lhs)[0 .. T.sizeof]; - auto b = (cast(ubyte*) &rhs)[0 .. T.sizeof]; - t[] = a[]; - a[] = b[]; - b[] = t[]; - } - } - else - { - //Avoid assigning overlapping arrays. Dynamic arrays are fine, because - //it's their ptr and length properties which get assigned rather - //than their elements when assigning them, but static arrays are value - //types and therefore all of their elements get copied as part of - //assigning them, which would be assigning overlapping arrays if lhs - //and rhs were the same array. - static if (isStaticArray!T) - { - if (lhs.ptr == rhs.ptr) - return; - } - - // For non-struct types, suffice to do the classic swap - auto tmp = lhs; - lhs = rhs; - rhs = tmp; - } -} - -// Not yet documented -void swap(T)(ref T lhs, ref T rhs) if (is(typeof(lhs.proxySwap(rhs)))) -{ - lhs.proxySwap(rhs); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int a = 42, b = 34; - swap(a, b); - assert(a == 34 && b == 42); - - static struct S { int x; char c; int[] y; } - S s1 = { 0, 'z', [ 1, 2 ] }; - S s2 = { 42, 'a', [ 4, 6 ] }; - //writeln(s2.tupleof.stringof); - swap(s1, s2); - assert(s1.x == 42); - assert(s1.c == 'a'); - assert(s1.y == [ 4, 6 ]); - - assert(s2.x == 0); - assert(s2.c == 'z'); - assert(s2.y == [ 1, 2 ]); - - immutable int imm1, imm2; - static assert(!__traits(compiles, swap(imm1, imm2))); -} - -unittest -{ - static struct NoCopy - { - this(this) { assert(0); } - int n; - string s; - } - NoCopy nc1, nc2; - nc1.n = 127; nc1.s = "abc"; - nc2.n = 513; nc2.s = "uvwxyz"; - swap(nc1, nc2); - assert(nc1.n == 513 && nc1.s == "uvwxyz"); - assert(nc2.n == 127 && nc2.s == "abc"); - swap(nc1, nc1); - swap(nc2, nc2); - assert(nc1.n == 513 && nc1.s == "uvwxyz"); - assert(nc2.n == 127 && nc2.s == "abc"); - - static struct NoCopyHolder - { - NoCopy noCopy; - } - NoCopyHolder h1, h2; - h1.noCopy.n = 31; h1.noCopy.s = "abc"; - h2.noCopy.n = 65; h2.noCopy.s = null; - swap(h1, h2); - assert(h1.noCopy.n == 65 && h1.noCopy.s == null); - assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); - swap(h1, h1); - swap(h2, h2); - assert(h1.noCopy.n == 65 && h1.noCopy.s == null); - assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); - - const NoCopy const1, const2; - static assert(!__traits(compiles, swap(const1, const2))); -} - -unittest -{ - //Bug# 4789 - int[1] s = [1]; - swap(s, s); -} - -unittest -{ - static struct NoAssign - { - int i; - void opAssign(NoAssign) @disable; - } - auto s1 = NoAssign(1); - auto s2 = NoAssign(2); - swap(s1, s2); - assert(s1.i == 2); - assert(s2.i == 1); -} - -unittest -{ - struct S - { - const int i; - } - S s; - static assert(!__traits(compiles, swap(s, s))); -} - -unittest -{ - //11853 - alias T = Tuple!(int, double); - static assert(isAssignable!T); -} - -unittest -{ - // 12024 - import std.datetime; - SysTime a, b; -} - -unittest // 9975 -{ - import std.exception : doesPointTo, mayPointTo; - static struct S2 - { - union - { - size_t sz; - string s; - } - } - S2 a , b; - a.sz = -1; - assert(!doesPointTo(a, b)); - assert( mayPointTo(a, b)); - swap(a, b); - - //Note: we can catch an error here, because there is no RAII in this test - import std.exception : assertThrown; - void* p, pp; - p = &p; - assertThrown!Error(move(p)); - assertThrown!Error(move(p, pp)); - assertThrown!Error(swap(p, pp)); -} - -void swapFront(R1, R2)(R1 r1, R2 r2) - if (isInputRange!R1 && isInputRange!R2) -{ - static if (is(typeof(swap(r1.front, r2.front)))) - { - swap(r1.front, r2.front); - } - else - { - auto t1 = moveFront(r1), t2 = moveFront(r2); - r1.front = move(t2); - r2.front = move(t1); - } -} - -/** -Forwards function arguments with saving ref-ness. -*/ -template forward(args...) -{ - import std.typetuple; - - static if (args.length) - { - alias arg = args[0]; - static if (__traits(isRef, arg)) - alias fwd = arg; - else - @property fwd()(){ return move(arg); } - alias forward = TypeTuple!(fwd, forward!(args[1..$])); - } - else - alias forward = TypeTuple!(); -} - -/// -unittest -{ - class C - { - static int foo(int n) { return 1; } - static int foo(ref int n) { return 2; } - } - int bar()(auto ref int x) { return C.foo(forward!x); } - - assert(bar(1) == 1); - int i; - assert(bar(i) == 2); -} - -/// -unittest -{ - void foo(int n, ref string s) { s = null; foreach (i; 0..n) s ~= "Hello"; } - - // forwards all arguments which are bound to parameter tuple - void bar(Args...)(auto ref Args args) { return foo(forward!args); } - - // forwards all arguments with swapping order - void baz(Args...)(auto ref Args args) { return foo(forward!args[$/2..$], forward!args[0..$/2]); } - - string s; - bar(1, s); - assert(s == "Hello"); - baz(s, 2); - assert(s == "HelloHello"); -} - -unittest -{ - auto foo(TL...)(auto ref TL args) - { - string result = ""; - foreach (i, _; args) - { - //pragma(msg, "[",i,"] ", __traits(isRef, args[i]) ? "L" : "R"); - result ~= __traits(isRef, args[i]) ? "L" : "R"; - } - return result; - } - - string bar(TL...)(auto ref TL args) - { - return foo(forward!args); - } - string baz(TL...)(auto ref TL args) - { - int x; - return foo(forward!args[3], forward!args[2], 1, forward!args[1], forward!args[0], x); - } - - struct S {} - S makeS(){ return S(); } - int n; - string s; - assert(bar(S(), makeS(), n, s) == "RRLL"); - assert(baz(S(), makeS(), n, s) == "LLRRRL"); -} - -unittest -{ - ref int foo(ref int a) { return a; } - ref int bar(Args)(auto ref Args args) - { - return foo(forward!args); - } - static assert(!__traits(compiles, { auto x1 = bar(3); })); // case of NG - int value = 3; - auto x2 = bar(value); // case of OK -} - -// splitter -/** -Splits a range using an element as a separator. This can be used with -any narrow string type or sliceable range type, but is most popular -with string types. - -Two adjacent separators are considered to surround an empty element in -the split range. Use $(D filter!(a => !a.empty)) on the result to compress -empty elements. - -If the empty range is given, the result is a range with one empty -element. If a range with one separator is given, the result is a range -with two empty elements. - -If splitting a string on whitespace and token compression is desired, -consider using $(D splitter) without specifying a separator (see overload -below). - -See also $(XREF regex, splitter) for a version that splits using a regular -expression defined separator. -*/ -auto splitter(Range, Separator)(Range r, Separator s) -if (is(typeof(ElementType!Range.init == Separator.init)) - && ((hasSlicing!Range && hasLength!Range) || isNarrowString!Range)) -{ - import std.conv : unsigned; - - static struct Result - { - private: - Range _input; - Separator _separator; - // Do we need hasLength!Range? popFront uses _input.length... - alias IndexType = typeof(unsigned(_input.length)); - enum IndexType _unComputed = IndexType.max - 1, _atEnd = IndexType.max; - IndexType _frontLength = _unComputed; - IndexType _backLength = _unComputed; - - static if (isNarrowString!Range) - { - size_t _separatorLength; - } - else - { - enum _separatorLength = 1; - } - - static if (isBidirectionalRange!Range) - { - static IndexType lastIndexOf(Range haystack, Separator needle) - { - auto r = haystack.retro().find(needle); - return r.retro().length - 1; - } - } - - public: - this(Range input, Separator separator) - { - _input = input; - _separator = separator; - - static if (isNarrowString!Range) - { - import std.utf : codeLength; - - _separatorLength = codeLength!(ElementEncodingType!Range)(separator); - } - if (_input.empty) - _frontLength = _atEnd; - } - - static if (isInfinite!Range) - { - enum bool empty = false; - } - else - { - @property bool empty() - { - return _frontLength == _atEnd; - } - } - - @property Range front() - { - assert(!empty); - if (_frontLength == _unComputed) - { - auto r = _input.find(_separator); - _frontLength = _input.length - r.length; - } - return _input[0 .. _frontLength]; - } - - void popFront() - { - assert(!empty); - if (_frontLength == _unComputed) - { - front; - } - assert(_frontLength <= _input.length); - if (_frontLength == _input.length) - { - // no more input and need to fetch => done - _frontLength = _atEnd; - - // Probably don't need this, but just for consistency: - _backLength = _atEnd; - } - else - { - _input = _input[_frontLength + _separatorLength .. _input.length]; - _frontLength = _unComputed; - } - } - - static if (isForwardRange!Range) - { - @property typeof(this) save() - { - auto ret = this; - ret._input = _input.save; - return ret; - } - } - - static if (isBidirectionalRange!Range) - { - @property Range back() - { - assert(!empty); - if (_backLength == _unComputed) - { - immutable lastIndex = lastIndexOf(_input, _separator); - if (lastIndex == -1) - { - _backLength = _input.length; - } - else - { - _backLength = _input.length - lastIndex - 1; - } - } - return _input[_input.length - _backLength .. _input.length]; - } - - void popBack() - { - assert(!empty); - if (_backLength == _unComputed) - { - // evaluate back to make sure it's computed - back; - } - assert(_backLength <= _input.length); - if (_backLength == _input.length) - { - // no more input and need to fetch => done - _frontLength = _atEnd; - _backLength = _atEnd; - } - else - { - _input = _input[0 .. _input.length - _backLength - _separatorLength]; - _backLength = _unComputed; - } - } - } - } - - return Result(r, s); -} - -/// -unittest -{ - assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ])); - int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; - int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; - assert(equal(splitter(a, 0), w)); - a = [ 0 ]; - assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ])); - a = [ 0, 1 ]; - assert(equal(splitter(a, 0), [ [], [1] ])); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ])); - assert(equal(splitter("žlutoučkýřkůň", 'ř'), [ "žlutoučký", "kůň" ])); - int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; - int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; - static assert(isForwardRange!(typeof(splitter(a, 0)))); - - // foreach (x; splitter(a, 0)) { - // writeln("[", x, "]"); - // } - assert(equal(splitter(a, 0), w)); - a = null; - assert(equal(splitter(a, 0), (int[][]).init)); - a = [ 0 ]; - assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ][])); - a = [ 0, 1 ]; - assert(equal(splitter(a, 0), [ [], [1] ][])); - - // Thoroughly exercise the bidirectional stuff. - auto str = "abc abcd abcde ab abcdefg abcdefghij ab ac ar an at ada"; - assert(equal( - retro(splitter(str, 'a')), - retro(array(splitter(str, 'a'))) - )); - - // Test interleaving front and back. - auto split = splitter(str, 'a'); - assert(split.front == ""); - assert(split.back == ""); - split.popBack(); - assert(split.back == "d"); - split.popFront(); - assert(split.front == "bc "); - assert(split.back == "d"); - split.popFront(); - split.popBack(); - assert(split.back == "t "); - split.popBack(); - split.popBack(); - split.popFront(); - split.popFront(); - assert(split.front == "b "); - assert(split.back == "r "); - - foreach (DummyType; AllDummyRanges) { // Bug 4408 - static if (isRandomAccessRange!DummyType) { - static assert(isBidirectionalRange!DummyType); - DummyType d; - auto s = splitter(d, 5); - assert(equal(s.front, [1,2,3,4])); - assert(equal(s.back, [6,7,8,9,10])); - - auto s2 = splitter(d, [4, 5]); - assert(equal(s2.front, [1,2,3])); - } - } -} -unittest -{ - auto L = retro(iota(1L, 10L)); - auto s = splitter(L, 5L); - assert(equal(s.front, [9L, 8L, 7L, 6L])); - s.popFront(); - assert(equal(s.front, [4L, 3L, 2L, 1L])); - s.popFront(); - assert(s.empty); -} - -/** -Splits a range using another range as a separator. This can be used -with any narrow string type or sliceable range type, but is most popular -with string types. - -Two adjacent separators are considered to surround an empty element in -the split range. Use $(D filter!(a => !a.empty)) on the result to compress -empty elements. - -See also $(XREF regex, splitter) for a version that splits using a regular -expression defined separator. - */ -auto splitter(Range, Separator)(Range r, Separator s) -if (is(typeof(Range.init.front == Separator.init.front) : bool) - && (hasSlicing!Range || isNarrowString!Range) - && isForwardRange!Separator - && (hasLength!Separator || isNarrowString!Separator)) -{ - import std.conv : unsigned; - - static struct Result - { - private: - Range _input; - Separator _separator; - alias RIndexType = typeof(unsigned(_input.length)); - // _frontLength == size_t.max means empty - RIndexType _frontLength = RIndexType.max; - static if (isBidirectionalRange!Range) - RIndexType _backLength = RIndexType.max; - - @property auto separatorLength() { return _separator.length; } - - void ensureFrontLength() - { - if (_frontLength != _frontLength.max) return; - assert(!_input.empty); - // compute front length - _frontLength = (_separator.empty) ? 1 : - _input.length - find(_input, _separator).length; - static if (isBidirectionalRange!Range) - if (_frontLength == _input.length) _backLength = _frontLength; - } - - void ensureBackLength() - { - static if (isBidirectionalRange!Range) - if (_backLength != _backLength.max) return; - assert(!_input.empty); - // compute back length - static if (isBidirectionalRange!Range && isBidirectionalRange!Separator) - { - _backLength = _input.length - - find(retro(_input), retro(_separator)).source.length; - } - } - - public: - this(Range input, Separator separator) - { - _input = input; - _separator = separator; - } - - @property Range front() - { - assert(!empty); - ensureFrontLength(); - return _input[0 .. _frontLength]; - } - - static if (isInfinite!Range) - { - enum bool empty = false; // Propagate infiniteness - } - else - { - @property bool empty() - { - return _frontLength == RIndexType.max && _input.empty; - } - } - - void popFront() - { - assert(!empty); - ensureFrontLength(); - if (_frontLength == _input.length) - { - // done, there's no separator in sight - _input = _input[_frontLength .. _frontLength]; - _frontLength = _frontLength.max; - static if (isBidirectionalRange!Range) - _backLength = _backLength.max; - return; - } - if (_frontLength + separatorLength == _input.length) - { - // Special case: popping the first-to-last item; there is - // an empty item right after this. - _input = _input[_input.length .. _input.length]; - _frontLength = 0; - static if (isBidirectionalRange!Range) - _backLength = 0; - return; - } - // Normal case, pop one item and the separator, get ready for - // reading the next item - _input = _input[_frontLength + separatorLength .. _input.length]; - // mark _frontLength as uninitialized - _frontLength = _frontLength.max; - } - - static if (isForwardRange!Range) - { - @property typeof(this) save() - { - auto ret = this; - ret._input = _input.save; - return ret; - } - } - - // Bidirectional functionality as suggested by Brad Roberts. - static if (isBidirectionalRange!Range && isBidirectionalRange!Separator) - { - //Deprecated. It will be removed in December 2015 - deprecated("splitter!(Range, Range) cannot be iterated backwards (due to separator overlap).") - @property Range back() - { - ensureBackLength(); - return _input[_input.length - _backLength .. _input.length]; - } - - //Deprecated. It will be removed in December 2015 - deprecated("splitter!(Range, Range) cannot be iterated backwards (due to separator overlap).") - void popBack() - { - ensureBackLength(); - if (_backLength == _input.length) - { - // done - _input = _input[0 .. 0]; - _frontLength = _frontLength.max; - _backLength = _backLength.max; - return; - } - if (_backLength + separatorLength == _input.length) - { - // Special case: popping the first-to-first item; there is - // an empty item right before this. Leave the separator in. - _input = _input[0 .. 0]; - _frontLength = 0; - _backLength = 0; - return; - } - // Normal case - _input = _input[0 .. _input.length - _backLength - separatorLength]; - _backLength = _backLength.max; - } - } - } - - return Result(r, s); -} - -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto s = ",abc, de, fg,hi,"; - auto sp0 = splitter(s, ','); - // //foreach (e; sp0) writeln("[", e, "]"); - assert(equal(sp0, ["", "abc", " de", " fg", "hi", ""][])); - - auto s1 = ", abc, de, fg, hi, "; - auto sp1 = splitter(s1, ", "); - //foreach (e; sp1) writeln("[", e, "]"); - assert(equal(sp1, ["", "abc", "de", " fg", "hi", ""][])); - static assert(isForwardRange!(typeof(sp1))); - - int[] a = [ 1, 2, 0, 3, 0, 4, 5, 0 ]; - int[][] w = [ [1, 2], [3], [4, 5], [] ]; - uint i; - foreach (e; splitter(a, 0)) - { - assert(i < w.length); - assert(e == w[i++]); - } - assert(i == w.length); - // // Now go back - // auto s2 = splitter(a, 0); - - // foreach (e; retro(s2)) - // { - // assert(i > 0); - // assert(equal(e, w[--i]), text(e)); - // } - // assert(i == 0); - - wstring names = ",peter,paul,jerry,"; - auto words = split(names, ","); - assert(walkLength(words) == 5, text(walkLength(words))); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto s6 = ","; - auto sp6 = splitter(s6, ','); - foreach (e; sp6) - { - //writeln("{", e, "}"); - } - assert(equal(sp6, ["", ""][])); -} - -unittest -{ - // Issue 10773 - auto s = splitter("abc", ""); - assert(s.equal(["a", "b", "c"])); -} - -unittest -{ - // Test by-reference separator - class RefSep { - string _impl; - this(string s) { _impl = s; } - @property empty() { return _impl.empty; } - @property auto front() { return _impl.front; } - void popFront() { _impl = _impl[1..$]; } - @property RefSep save() { return new RefSep(_impl); } - @property auto length() { return _impl.length; } - } - auto sep = new RefSep("->"); - auto data = "i->am->pointing"; - auto words = splitter(data, sep); - assert(words.equal([ "i", "am", "pointing" ])); -} - -///ditto -auto splitter(alias isTerminator, Range)(Range input) -if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(input.front)))) -{ - return SplitterResult!(unaryFun!isTerminator, Range)(input); -} - -private struct SplitterResult(alias isTerminator, Range) -{ - enum fullSlicing = (hasLength!Range && hasSlicing!Range) || isSomeString!Range; - - private Range _input; - private size_t _end = 0; - static if(!fullSlicing) - private Range _next; - - private void findTerminator() - { - static if (fullSlicing) - { - auto r = find!isTerminator(_input.save); - _end = _input.length - r.length; - } - else - for ( _end = 0; !_next.empty ; _next.popFront) - { - if (isTerminator(_next.front)) - break; - ++_end; - } - } - - this(Range input) - { - _input = input; - static if(!fullSlicing) - _next = _input.save; - - if (!_input.empty) - findTerminator(); - else - _end = size_t.max; - } - - static if (isInfinite!Range) - { - enum bool empty = false; // Propagate infiniteness. - } - else - { - @property bool empty() - { - return _end == size_t.max; - } - } - - @property auto front() - { - version(assert) - { - import core.exception : RangeError; - if (empty) - throw new RangeError(); - } - static if (fullSlicing) - return _input[0 .. _end]; - else - return _input.takeExactly(_end); - } - - void popFront() - { - version(assert) - { - import core.exception : RangeError; - if (empty) - throw new RangeError(); - } - - static if (fullSlicing) - { - _input = _input[_end .. _input.length]; - if (_input.empty) - { - _end = size_t.max; - return; - } - _input.popFront(); - } - else - { - if (_next.empty) - { - _input = _next; - _end = size_t.max; - return; - } - _next.popFront(); - _input = _next.save; - } - findTerminator(); - } - - @property typeof(this) save() - { - auto ret = this; - ret._input = _input.save; - static if (!fullSlicing) - ret._next = _next.save; - return ret; - } -} - -unittest -{ - auto L = iota(1L, 10L); - auto s = splitter(L, [5L, 6L]); - assert(equal(s.front, [1L, 2L, 3L, 4L])); - s.popFront(); - assert(equal(s.front, [7L, 8L, 9L])); - s.popFront(); - assert(s.empty); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - void compare(string sentence, string[] witness) - { - auto r = splitter!"a == ' '"(sentence); - assert(equal(r.save, witness), algoFormat("got: %(%s, %) expected: %(%s, %)", r, witness)); - } - - compare(" Mary has a little lamb. ", - ["", "Mary", "", "has", "a", "little", "lamb.", "", "", ""]); - compare("Mary has a little lamb. ", - ["Mary", "", "has", "a", "little", "lamb.", "", "", ""]); - compare("Mary has a little lamb.", - ["Mary", "", "has", "a", "little", "lamb."]); - compare("", (string[]).init); - compare(" ", ["", ""]); - - static assert(isForwardRange!(typeof(splitter!"a == ' '"("ABC")))); - - foreach (DummyType; AllDummyRanges) - { - static if (isRandomAccessRange!DummyType) - { - auto rangeSplit = splitter!"a == 5"(DummyType.init); - assert(equal(rangeSplit.front, [1,2,3,4])); - rangeSplit.popFront(); - assert(equal(rangeSplit.front, [6,7,8,9,10])); - } - } -} - -unittest -{ - struct Entry - { - int low; - int high; - int[][] result; - } - Entry[] entries = [ - Entry(0, 0, []), - Entry(0, 1, [[0]]), - Entry(1, 2, [[], []]), - Entry(2, 7, [[2], [4], [6]]), - Entry(1, 8, [[], [2], [4], [6], []]), - ]; - foreach ( entry ; entries ) - { - auto a = iota(entry.low, entry.high).filter!"true"(); - auto b = splitter!"a%2"(a); - assert(equal!equal(b.save, entry.result), algoFormat("got: %(%s, %) expected: %(%s, %)", b, entry.result)); - } -} - -unittest -{ - import std.uni : isWhite; - - //@@@6791@@@ - assert(equal(std.array.splitter("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"])); - assert(equal(splitter!(std.uni.isWhite)("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"])); - assert(equal(splitter!"a=='本'"("日本語"), ["日", "語"])); -} - -/++ -Lazily splits the string $(D s) into words, using whitespace as the delimiter. - -This function is string specific and, contrary to -$(D splitter!(std.uni.isWhite)), runs of whitespace will be merged together -(no empty tokens will be produced). - +/ -auto splitter(C)(C[] s) -if (isSomeChar!C) -{ - static struct Result - { - private: - import core.exception; - C[] _s; - size_t _frontLength; - - void getFirst() pure @safe - { - import std.uni : isWhite; - - auto r = find!(std.uni.isWhite)(_s); - _frontLength = _s.length - r.length; - } - - public: - this(C[] s) pure @safe - { - import std.string; - _s = s.strip(); - getFirst(); - } - - @property C[] front() pure @safe - { - version(assert) if (empty) throw new RangeError(); - return _s[0 .. _frontLength]; - } - - void popFront() pure @safe - { - import std.string : stripLeft; - version(assert) if (empty) throw new RangeError(); - _s = _s[_frontLength .. $].stripLeft(); - getFirst(); - } - - @property bool empty() const @safe pure nothrow - { - return _s.empty; - } - - @property inout(Result) save() inout @safe pure nothrow - { - return this; - } - } - return Result(s); -} - -/// -@safe pure unittest -{ - auto a = " a bcd ef gh "; - assert(equal(splitter(a), ["a", "bcd", "ef", "gh"][])); -} - -@safe pure unittest -{ - foreach(S; TypeTuple!(string, wstring, dstring)) - { - import std.conv : to; - S a = " a bcd ef gh "; - assert(equal(splitter(a), [to!S("a"), to!S("bcd"), to!S("ef"), to!S("gh")])); - a = ""; - assert(splitter(a).empty); - } - - immutable string s = " a bcd ef gh "; - assert(equal(splitter(s), ["a", "bcd", "ef", "gh"][])); -} - -unittest -{ - import std.conv : to; - import std.string : strip; - - // TDPL example, page 8 - uint[string] dictionary; - char[][3] lines; - lines[0] = "line one".dup; - lines[1] = "line \ttwo".dup; - lines[2] = "yah last line\ryah".dup; - foreach (line; lines) { - foreach (word; std.array.splitter(std.string.strip(line))) { - if (word in dictionary) continue; // Nothing to do - auto newID = dictionary.length; - dictionary[to!string(word)] = cast(uint)newID; - } - } - assert(dictionary.length == 5); - assert(dictionary["line"]== 0); - assert(dictionary["one"]== 1); - assert(dictionary["two"]== 2); - assert(dictionary["yah"]== 3); - assert(dictionary["last"]== 4); -} - -unittest -{ - import std.conv : text; - import std.string : split; - - // Check consistency: - // All flavors of split should produce the same results - foreach (input; [(int[]).init, - [0], - [0, 1, 0], - [1, 1, 0, 0, 1, 1], - ]) - { - foreach (s; [0, 1]) - { - auto result = split(input, s); - - assert(equal(result, split(input, [s])), algoFormat(`"[%(%s,%)]"`, split(input, [s]))); - //assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented - assert(equal(result, split!((a) => a == s)(input)), text(split!((a) => a == s)(input))); - - //assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented - //assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented - //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented - assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"()))); - - assert(equal(result, splitter(input, s))); - assert(equal(result, splitter(input, [s]))); - //assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented - assert(equal(result, splitter!((a) => a == s)(input))); - - //assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented - //assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented - //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented - assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"()))); - } - } - foreach (input; [string.init, - " ", - " hello ", - "hello hello", - " hello what heck this ? " - ]) - { - foreach (s; [' ', 'h']) - { - auto result = split(input, s); - - assert(equal(result, split(input, [s]))); - //assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented - assert(equal(result, split!((a) => a == s)(input))); - - //assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented - //assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented - //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented - assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"()))); - - assert(equal(result, splitter(input, s))); - assert(equal(result, splitter(input, [s]))); - //assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented - assert(equal(result, splitter!((a) => a == s)(input))); - - //assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented - //assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented - //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented - assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"()))); - } - } -} -// joiner -/** -Lazily joins a range of ranges with a separator. The separator itself -is a range. If you do not provide a separator, then the ranges are -joined directly without anything in between them. - - */ -auto joiner(RoR, Separator)(RoR r, Separator sep) -if (isInputRange!RoR && isInputRange!(ElementType!RoR) - && isForwardRange!Separator - && is(ElementType!Separator : ElementType!(ElementType!RoR))) -{ - static struct Result - { - private RoR _items; - private ElementType!RoR _current; - private Separator _sep, _currentSep; - - // This is a mixin instead of a function for the following reason (as - // explained by Kenji Hara): "This is necessary from 2.061. If a - // struct has a nested struct member, it must be directly initialized - // in its constructor to avoid leaving undefined state. If you change - // setItem to a function, the initialization of _current field is - // wrapped into private member function, then compiler could not detect - // that is correctly initialized while constructing. To avoid the - // compiler error check, string mixin is used." - private enum setItem = - q{ - if (!_items.empty) - { - // If we're exporting .save, we must not consume any of the - // subranges, since RoR.save does not guarantee that the states - // of the subranges are also saved. - static if (isForwardRange!RoR && - isForwardRange!(ElementType!RoR)) - _current = _items.front.save; - else - _current = _items.front; - } - }; - - private void useSeparator() - { - // Separator must always come after an item. - assert(_currentSep.empty && !_items.empty, - "joiner: internal error"); - _items.popFront(); - - // If there are no more items, we're done, since separators are not - // terminators. - if (_items.empty) return; - - if (_sep.empty) - { - // Advance to the next range in the - // input - while (_items.front.empty) - { - _items.popFront(); - if (_items.empty) return; - } - mixin(setItem); - } - else - { - _currentSep = _sep.save; - assert(!_currentSep.empty); - } - } - - private enum useItem = - q{ - // FIXME: this will crash if either _currentSep or _current are - // class objects, because .init is null when the ctor invokes this - // mixin. - //assert(_currentSep.empty && _current.empty, - // "joiner: internal error"); - - // Use the input - if (_items.empty) return; - mixin(setItem); - if (_current.empty) - { - // No data in the current item - toggle to use the separator - useSeparator(); - } - }; - - this(RoR items, Separator sep) - { - _items = items; - _sep = sep; - - //mixin(useItem); // _current should be initialized in place - if (_items.empty) - _current = _current.init; // set invalid state - else - { - // If we're exporting .save, we must not consume any of the - // subranges, since RoR.save does not guarantee that the states - // of the subranges are also saved. - static if (isForwardRange!RoR && - isForwardRange!(ElementType!RoR)) - _current = _items.front.save; - else - _current = _items.front; - - if (_current.empty) - { - // No data in the current item - toggle to use the separator - useSeparator(); - } - } - } - - @property auto empty() - { - return _items.empty; - } - - @property ElementType!(ElementType!RoR) front() - { - if (!_currentSep.empty) return _currentSep.front; - assert(!_current.empty); - return _current.front; - } - - void popFront() - { - assert(!_items.empty); - // Using separator? - if (!_currentSep.empty) - { - _currentSep.popFront(); - if (!_currentSep.empty) return; - mixin(useItem); - } - else - { - // we're using the range - _current.popFront(); - if (!_current.empty) return; - useSeparator(); - } - } - - static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) - { - @property auto save() - { - Result copy = this; - copy._items = _items.save; - copy._current = _current.save; - copy._sep = _sep.save; - copy._currentSep = _currentSep.save; - return copy; - } - } - } - return Result(r, sep); -} - -/// -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - static assert(isInputRange!(typeof(joiner([""], "")))); - static assert(isForwardRange!(typeof(joiner([""], "")))); - assert(equal(joiner([""], "xyz"), ""), text(joiner([""], "xyz"))); - assert(equal(joiner(["", ""], "xyz"), "xyz"), text(joiner(["", ""], "xyz"))); - assert(equal(joiner(["", "abc"], "xyz"), "xyzabc")); - assert(equal(joiner(["abc", ""], "xyz"), "abcxyz")); - assert(equal(joiner(["abc", "def"], "xyz"), "abcxyzdef")); - assert(equal(joiner(["Mary", "has", "a", "little", "lamb"], "..."), - "Mary...has...a...little...lamb")); - assert(equal(joiner(["abc", "def"]), "abcdef")); -} - -unittest -{ - // joiner() should work for non-forward ranges too. - InputRange!string r = inputRangeObject(["abc", "def"]); - assert (equal(joiner(r, "xyz"), "abcxyzdef")); -} - -unittest -{ - // Related to issue 8061 - auto r = joiner([ - inputRangeObject("abc"), - inputRangeObject("def"), - ], "-*-"); - - assert(equal(r, "abc-*-def")); - - // Test case where separator is specified but is empty. - auto s = joiner([ - inputRangeObject("abc"), - inputRangeObject("def"), - ], ""); - - assert(equal(s, "abcdef")); - - // Test empty separator with some empty elements - auto t = joiner([ - inputRangeObject("abc"), - inputRangeObject(""), - inputRangeObject("def"), - inputRangeObject(""), - ], ""); - - assert(equal(t, "abcdef")); - - // Test empty elements with non-empty separator - auto u = joiner([ - inputRangeObject(""), - inputRangeObject("abc"), - inputRangeObject(""), - inputRangeObject("def"), - inputRangeObject(""), - ], "+-"); - - assert(equal(u, "+-abc+-+-def+-")); - - // Issue 13441: only(x) as separator - string[][] lines = [null]; - lines - .joiner(only("b")) - .array(); -} - -unittest -{ - // Transience correctness test - struct TransientRange - { - int[][] src; - int[] buf; - - this(int[][] _src) - { - src = _src; - buf.length = 100; - } - @property bool empty() { return src.empty; } - @property int[] front() - { - assert(src.front.length <= buf.length); - buf[0 .. src.front.length] = src.front[0..$]; - return buf[0 .. src.front.length]; - } - void popFront() { src.popFront(); } - } - - // Test embedded empty elements - auto tr1 = TransientRange([[], [1,2,3], [], [4]]); - assert(equal(joiner(tr1, [0]), [0,1,2,3,0,0,4])); - - // Test trailing empty elements - auto tr2 = TransientRange([[], [1,2,3], []]); - assert(equal(joiner(tr2, [0]), [0,1,2,3,0])); - - // Test no empty elements - auto tr3 = TransientRange([[1,2], [3,4]]); - assert(equal(joiner(tr3, [0,1]), [1,2,0,1,3,4])); - - // Test consecutive empty elements - auto tr4 = TransientRange([[1,2], [], [], [], [3,4]]); - assert(equal(joiner(tr4, [0,1]), [1,2,0,1,0,1,0,1,0,1,3,4])); - - // Test consecutive trailing empty elements - auto tr5 = TransientRange([[1,2], [3,4], [], []]); - assert(equal(joiner(tr5, [0,1]), [1,2,0,1,3,4,0,1,0,1])); -} - -/// Ditto -auto joiner(RoR)(RoR r) -if (isInputRange!RoR && isInputRange!(ElementType!RoR)) -{ - static struct Result - { - private: - RoR _items; - ElementType!RoR _current; - enum prepare = - q{ - // Skip over empty subranges. - if (_items.empty) return; - while (_items.front.empty) - { - _items.popFront(); - if (_items.empty) return; - } - // We cannot export .save method unless we ensure subranges are not - // consumed when a .save'd copy of ourselves is iterated over. So - // we need to .save each subrange we traverse. - static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) - _current = _items.front.save; - else - _current = _items.front; - }; - public: - this(RoR r) - { - _items = r; - //mixin(prepare); // _current should be initialized in place - - // Skip over empty subranges. - while (!_items.empty && _items.front.empty) - _items.popFront(); - - if (_items.empty) - _current = _current.init; // set invalid state - else - { - // We cannot export .save method unless we ensure subranges are not - // consumed when a .save'd copy of ourselves is iterated over. So - // we need to .save each subrange we traverse. - static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) - _current = _items.front.save; - else - _current = _items.front; - } - } - static if (isInfinite!RoR) - { - enum bool empty = false; - } - else - { - @property auto empty() - { - return _items.empty; - } - } - @property auto ref front() - { - assert(!empty); - return _current.front; - } - void popFront() - { - assert(!_current.empty); - _current.popFront(); - if (_current.empty) - { - assert(!_items.empty); - _items.popFront(); - mixin(prepare); - } - } - static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) - { - @property auto save() - { - Result copy = this; - copy._items = _items.save; - copy._current = _current.save; - return copy; - } - } - } - return Result(r); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - static assert(isInputRange!(typeof(joiner([""])))); - static assert(isForwardRange!(typeof(joiner([""])))); - assert(equal(joiner([""]), "")); - assert(equal(joiner(["", ""]), "")); - assert(equal(joiner(["", "abc"]), "abc")); - assert(equal(joiner(["abc", ""]), "abc")); - assert(equal(joiner(["abc", "def"]), "abcdef")); - assert(equal(joiner(["Mary", "has", "a", "little", "lamb"]), - "Maryhasalittlelamb")); - assert(equal(joiner(std.range.repeat("abc", 3)), "abcabcabc")); - - // joiner allows in-place mutation! - auto a = [ [1, 2, 3], [42, 43] ]; - auto j = joiner(a); - j.front = 44; - assert(a == [ [44, 2, 3], [42, 43] ]); - - // bugzilla 8240 - assert(equal(joiner([inputRangeObject("")]), "")); - - // issue 8792 - auto b = [[1], [2], [3]]; - auto jb = joiner(b); - auto js = jb.save; - assert(equal(jb, js)); - - auto js2 = jb.save; - jb.popFront(); - assert(!equal(jb, js)); - assert(equal(js2, js)); - js.popFront(); - assert(equal(jb, js)); - assert(!equal(js2, js)); -} - -unittest -{ - struct TransientRange - { - int[] _buf; - int[][] _values; - this(int[][] values) - { - _values = values; - _buf = new int[128]; - } - @property bool empty() - { - return _values.length == 0; - } - @property auto front() - { - foreach (i; 0 .. _values.front.length) - { - _buf[i] = _values[0][i]; - } - return _buf[0 .. _values.front.length]; - } - void popFront() - { - _values = _values[1 .. $]; - } - } - - auto rr = TransientRange([[1,2], [3,4,5], [], [6,7]]); - - // Can't use array() or equal() directly because they fail with transient - // .front. - int[] result; - foreach (c; rr.joiner()) { - result ~= c; - } - - assert(equal(result, [1,2,3,4,5,6,7])); -} - -unittest -{ - struct TransientRange - { - dchar[] _buf; - dstring[] _values; - this(dstring[] values) - { - _buf.length = 128; - _values = values; - } - @property bool empty() - { - return _values.length == 0; - } - @property auto front() - { - foreach (i; 0 .. _values.front.length) - { - _buf[i] = _values[0][i]; - } - return _buf[0 .. _values.front.length]; - } - void popFront() - { - _values = _values[1 .. $]; - } - } - - auto rr = TransientRange(["abc"d, "12"d, "def"d, "34"d]); - - // Can't use array() or equal() directly because they fail with transient - // .front. - dchar[] result; - foreach (c; rr.joiner()) { - result ~= c; - } - - assert(equal(result, "abc12def34"d), - "Unexpected result: '%s'"d.algoFormat(result)); -} - -// Issue 8061 -unittest -{ - import std.conv : to; - - auto r = joiner([inputRangeObject("ab"), inputRangeObject("cd")]); - assert(isForwardRange!(typeof(r))); - - auto str = to!string(r); - assert(str == "abcd"); -} - -// uniq -/** -Iterates unique consecutive elements of the given range (functionality -akin to the $(WEB wikipedia.org/wiki/_Uniq, _uniq) system -utility). Equivalence of elements is assessed by using the predicate -$(D pred), by default $(D "a == b"). If the given range is -bidirectional, $(D uniq) also yields a bidirectional range. -*/ -auto uniq(alias pred = "a == b", Range)(Range r) -if (isInputRange!Range && is(typeof(binaryFun!pred(r.front, r.front)) == bool)) -{ - return UniqResult!(binaryFun!pred, Range)(r); -} - -/// -unittest -{ - int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; - assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][])); -} - -private struct UniqResult(alias pred, Range) -{ - Range _input; - - this(Range input) - { - _input = input; - } - - auto opSlice() - { - return this; - } - - void popFront() - { - auto last = _input.front; - do - { - _input.popFront(); - } - while (!_input.empty && pred(last, _input.front)); - } - - @property ElementType!Range front() { return _input.front; } - - static if (isBidirectionalRange!Range) - { - void popBack() - { - auto last = _input.back; - do - { - _input.popBack(); - } - while (!_input.empty && pred(last, _input.back)); - } - - @property ElementType!Range back() { return _input.back; } - } - - static if (isInfinite!Range) - { - enum bool empty = false; // Propagate infiniteness. - } - else - { - @property bool empty() { return _input.empty; } - } - - static if (isForwardRange!Range) { - @property typeof(this) save() { - return typeof(this)(_input.save); - } - } -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; - auto r = uniq(arr); - static assert(isForwardRange!(typeof(r))); - - assert(equal(r, [ 1, 2, 3, 4, 5 ][])); - assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][]))); - - foreach (DummyType; AllDummyRanges) { - DummyType d; - auto u = uniq(d); - assert(equal(u, [1,2,3,4,5,6,7,8,9,10])); - - static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u))); - - static if (d.rt >= RangeType.Bidirectional) { - assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1])); - } - } -} - -// group -/** -Similarly to $(D uniq), $(D group) iterates unique consecutive -elements of the given range. The element type is $(D -Tuple!(ElementType!R, uint)) because it includes the count of -equivalent elements seen. Equivalence of elements is assessed by using -the predicate $(D pred), by default $(D "a == b"). - -$(D Group) is an input range if $(D R) is an input range, and a -forward range in all other cases. -*/ -struct Group(alias pred, R) if (isInputRange!R) -{ - private R _input; - private Tuple!(ElementType!R, uint) _current; - private alias comp = binaryFun!pred; - - this(R input) - { - _input = input; - if (!_input.empty) popFront(); - } - - void popFront() - { - if (_input.empty) - { - _current[1] = 0; - } - else - { - _current = tuple(_input.front, 1u); - _input.popFront(); - while (!_input.empty && comp(_current[0], _input.front)) - { - ++_current[1]; - _input.popFront(); - } - } - } - - static if (isInfinite!R) - { - enum bool empty = false; // Propagate infiniteness. - } - else - { - @property bool empty() - { - return _current[1] == 0; - } - } - - @property ref Tuple!(ElementType!R, uint) front() - { - assert(!empty); - return _current; - } - - static if (isForwardRange!R) { - @property typeof(this) save() { - typeof(this) ret = this; - ret._input = this._input.save; - ret._current = this._current; - return ret; - } - } -} - -/// Ditto -Group!(pred, Range) group(alias pred = "a == b", Range)(Range r) -{ - return typeof(return)(r); -} - -/// -unittest -{ - int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; - assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u), - tuple(4, 3u), tuple(5, 1u) ][])); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; - assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u), - tuple(4, 3u), tuple(5, 1u) ][])); - static assert(isForwardRange!(typeof(group(arr)))); - - foreach (DummyType; AllDummyRanges) { - DummyType d; - auto g = group(d); - - static assert(d.rt == RangeType.Input || isForwardRange!(typeof(g))); - - assert(equal(g, [tuple(1, 1u), tuple(2, 1u), tuple(3, 1u), tuple(4, 1u), - tuple(5, 1u), tuple(6, 1u), tuple(7, 1u), tuple(8, 1u), - tuple(9, 1u), tuple(10, 1u)])); - } -} - -// overwriteAdjacent -/* -Reduces $(D r) by shifting it to the left until no adjacent elements -$(D a), $(D b) remain in $(D r) such that $(D pred(a, b)). Shifting is -performed by evaluating $(D move(source, target)) as a primitive. The -algorithm is stable and runs in $(BIGOH r.length) time. Returns the -reduced range. - -The default $(XREF _algorithm, move) performs a potentially -destructive assignment of $(D source) to $(D target), so the objects -beyond the returned range should be considered "empty". By default $(D -pred) compares for equality, in which case $(D overwriteAdjacent) -collapses adjacent duplicate elements to one (functionality akin to -the $(WEB wikipedia.org/wiki/Uniq, uniq) system utility). - -Example: ----- -int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; -auto r = overwriteAdjacent(arr); -assert(r == [ 1, 2, 3, 4, 5 ]); ----- -*/ -// Range overwriteAdjacent(alias pred, alias move, Range)(Range r) -// { -// if (r.empty) return r; -// //auto target = begin(r), e = end(r); -// auto target = r; -// auto source = r; -// source.popFront(); -// while (!source.empty) -// { -// if (!pred(target.front, source.front)) -// { -// target.popFront(); -// continue; -// } -// // found an equal *source and *target -// for (;;) -// { -// //@@@ -// //move(source.front, target.front); -// target[0] = source[0]; -// source.popFront(); -// if (source.empty) break; -// if (!pred(target.front, source.front)) target.popFront(); -// } -// break; -// } -// return range(begin(r), target + 1); -// } - -// /// Ditto -// Range overwriteAdjacent( -// string fun = "a == b", -// alias move = .move, -// Range)(Range r) -// { -// return .overwriteAdjacent!(binaryFun!(fun), move, Range)(r); -// } - -// unittest -// { -// int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; -// auto r = overwriteAdjacent(arr); -// assert(r == [ 1, 2, 3, 4, 5 ]); -// assert(arr == [ 1, 2, 3, 4, 5, 3, 4, 4, 4, 5 ]); - -// } - -// find -/** -Finds an individual element in an input range. Elements of $(D -haystack) are compared with $(D needle) by using predicate $(D -pred). Performs $(BIGOH walkLength(haystack)) evaluations of $(D -pred). - -To _find the last occurrence of $(D needle) in $(D haystack), call $(D -find(retro(haystack), needle)). See $(XREF range, retro). - -Params: - -haystack = The range searched in. - -needle = The element searched for. - -Constraints: - -$(D isInputRange!InputRange && is(typeof(binaryFun!pred(haystack.front, needle) -: bool))) - -Returns: - -$(D haystack) advanced such that $(D binaryFun!pred(haystack.front, -needle)) is $(D true) (if no such position exists, returns $(D -haystack) after exhaustion). - -See_Also: - $(WEB sgi.com/tech/stl/_find.html, STL's _find) - */ -InputRange find(alias pred = "a == b", InputRange, Element)(InputRange haystack, Element needle) -if (isInputRange!InputRange && - is (typeof(binaryFun!pred(haystack.front, needle)) : bool)) -{ - alias R = InputRange; - alias E = Element; - alias predFun = binaryFun!pred; - static if (is(typeof(pred == "a == b"))) - enum isDefaultPred = pred == "a == b"; - else - enum isDefaultPred = false; - enum isIntegralNeedle = isSomeChar!E || isIntegral!E || isBoolean!E; - - alias EType = ElementType!R; - - static if (isNarrowString!R) - { - alias EEType = ElementEncodingType!R; - alias UEEType = Unqual!EEType; - - //These are two special cases which can search without decoding the UTF stream. - static if (isDefaultPred && isIntegralNeedle) - { - import std.utf : canSearchInCodeUnits; - - //This special case deals with UTF8 search, when the needle - //is represented by a single code point. - //Note: "needle <= 0x7F" properly handles sign via unsigned promotion - static if (is(UEEType == char)) - { - if (!__ctfe && canSearchInCodeUnits!char(needle)) - { - static R trustedMemchr(ref R haystack, ref E needle) @trusted nothrow pure - { - import core.stdc.string : memchr; - auto ptr = memchr(haystack.ptr, needle, haystack.length); - return ptr ? - haystack[ptr - haystack.ptr .. $] : - haystack[$ .. $]; - } - return trustedMemchr(haystack, needle); - } - } - - //Ditto, but for UTF16 - static if (is(UEEType == wchar)) - { - if (canSearchInCodeUnits!wchar(needle)) - { - foreach (i, ref EEType e; haystack) - { - if (e == needle) - return haystack[i .. $]; - } - return haystack[$ .. $]; - } - } - } - - //Previous conditonal optimizations did not succeed. Fallback to - //unconditional implementations - static if (isDefaultPred) - { - import std.utf : encode; - - //In case of default pred, it is faster to do string/string search. - UEEType[is(UEEType == char) ? 4 : 2] buf; - - size_t len = encode(buf, needle); - //TODO: Make find!(R, R) @safe - R trustedFindRR(ref R haystack, UEEType[] needle) @trusted pure - { - return cast(R) std.algorithm.find(haystack, needle); - } - return trustedFindRR(haystack, buf[0 .. len]); - } - else - { - import std.utf : decode; - - //Explicit pred: we must test each character by the book. - //We choose a manual decoding approach, because it is faster than - //the built-in foreach, or doing a front/popFront for-loop. - immutable len = haystack.length; - size_t i = 0, next = 0; - while (next < len) - { - if (predFun(decode(haystack, next), needle)) - return haystack[i .. $]; - i = next; - } - return haystack[$ .. $]; - } - } - else static if (isArray!R) - { - //10403 optimization - static if (isDefaultPred && isIntegral!EType && EType.sizeof == 1 && isIntegralNeedle) - { - R findHelper(ref R haystack, ref E needle) @trusted nothrow pure - { - import core.stdc.string : memchr; - - EType* ptr = null; - //Note: we use "min/max" to handle sign mismatch. - if (min(EType.min, needle) == EType.min && - max(EType.max, needle) == EType.max) - { - ptr = cast(EType*) memchr(haystack.ptr, needle, - haystack.length); - } - - return ptr ? - haystack[ptr - haystack.ptr .. $] : - haystack[$ .. $]; - } - - if (!__ctfe) - return findHelper(haystack, needle); - } - - //Default implementation. - foreach (i, ref e; haystack) - if (predFun(e, needle)) - return haystack[i .. $]; - return haystack[$ .. $]; - } - else - { - //Everything else. Walk. - for ( ; !haystack.empty; haystack.popFront() ) - { - if (predFun(haystack.front, needle)) - break; - } - return haystack; - } -} - -/// -unittest -{ - import std.container : SList; - - assert(find("hello, world", ',') == ", world"); - assert(find([1, 2, 3, 5], 4) == []); - assert(equal(find(SList!int(1, 2, 3, 4, 5)[], 4), SList!int(4, 5)[])); - assert(find!"a > b"([1, 2, 3, 5], 2) == [3, 5]); - - auto a = [ 1, 2, 3 ]; - assert(find(a, 5).empty); // not found - assert(!find(a, 2).empty); // found - - // Case-insensitive find of a string - string[] s = [ "Hello", "world", "!" ]; - assert(!find!("toLower(a) == b")(s, "hello").empty); -} - -unittest -{ - import std.container : SList; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto lst = SList!int(1, 2, 5, 7, 3); - assert(lst.front == 1); - auto r = find(lst[], 5); - assert(equal(r, SList!int(5, 7, 3)[])); - assert(find([1, 2, 3, 5], 4).empty); - assert(equal(find!"a>b"("hello", 'k'), "llo")); -} - -@safe pure nothrow unittest -{ - int[] a1 = [1, 2, 3]; - assert(!find ([1, 2, 3], 2).empty); - assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty); - ubyte[] a2 = [1, 2, 3]; - ubyte b2 = 2; - assert(!find ([1, 2, 3], 2).empty); - assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty); -} - -@safe pure unittest -{ - foreach(R; TypeTuple!(string, wstring, dstring)) - { - foreach(E; TypeTuple!(char, wchar, dchar)) - { - R r1 = "hello world"; - E e1 = 'w'; - assert(find ("hello world", 'w') == "world"); - assert(find!((a,b)=>a==b)("hello world", 'w') == "world"); - R r2 = "日c語"; - E e2 = 'c'; - assert(find ("日c語", 'c') == "c語"); - assert(find!((a,b)=>a==b)("日c語", 'c') == "c語"); - static if (E.sizeof >= 2) - { - R r3 = "hello world"; - E e3 = 'w'; - assert(find ("日本語", '本') == "本語"); - assert(find!((a,b)=>a==b)("日本語", '本') == "本語"); - } - } - } -} - -unittest -{ - //CTFE - static assert (find("abc", 'b') == "bc"); - static assert (find("日b語", 'b') == "b語"); - static assert (find("日本語", '本') == "本語"); - static assert (find([1, 2, 3], 2) == [2, 3]); - - int[] a1 = [1, 2, 3]; - static assert(find ([1, 2, 3], 2)); - static assert(find!((a,b)=>a==b)([1, 2, 3], 2)); - ubyte[] a2 = [1, 2, 3]; - ubyte b2 = 2; - static assert(find ([1, 2, 3], 2)); - static assert(find!((a,b)=>a==b)([1, 2, 3], 2)); -} - -unittest -{ - import std.exception : assertCTFEable; - - void dg() @safe pure nothrow - { - byte[] sarr = [1, 2, 3, 4]; - ubyte[] uarr = [1, 2, 3, 4]; - foreach(arr; TypeTuple!(sarr, uarr)) - { - foreach(T; TypeTuple!(byte, ubyte, int, uint)) - { - assert(find(arr, cast(T) 3) == arr[2 .. $]); - assert(find(arr, cast(T) 9) == arr[$ .. $]); - } - assert(find(arr, 256) == arr[$ .. $]); - } - } - dg(); - assertCTFEable!dg; -} - -unittest -{ - // Bugzilla 11603 - enum Foo : ubyte { A } - assert([Foo.A].find(Foo.A).empty == false); - - ubyte x = 0; - assert([x].find(x).empty == false); -} - -/** -Finds a forward range in another. Elements are compared for -equality. Performs $(BIGOH walkLength(haystack) * walkLength(needle)) -comparisons in the worst case. Specializations taking advantage of -bidirectional or random access (where present) may accelerate search -depending on the statistics of the two ranges' content. - -Params: - -haystack = The range searched in. - -needle = The range searched for. - -Constraints: - -$(D isForwardRange!R1 && isForwardRange!R2 && -is(typeof(binaryFun!pred(haystack.front, needle.front) : bool))) - -Returns: - -$(D haystack) advanced such that $(D needle) is a prefix of it (if no -such position exists, returns $(D haystack) advanced to termination). - */ -R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) -if (isForwardRange!R1 && isForwardRange!R2 - && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool) - && !isRandomAccessRange!R1) -{ - static if (is(typeof(pred == "a == b")) && pred == "a == b" && isSomeString!R1 && isSomeString!R2 - && haystack[0].sizeof == needle[0].sizeof) - { - //return cast(R1) find(representation(haystack), representation(needle)); - // Specialization for simple string search - alias Representation = - Select!(haystack[0].sizeof == 1, ubyte[], - Select!(haystack[0].sizeof == 2, ushort[], uint[])); - // Will use the array specialization - return cast(R1) .find!(pred, Representation, Representation) - (cast(Representation) haystack, cast(Representation) needle); - } - else - { - return simpleMindedFind!pred(haystack, needle); - } -} - -/// -unittest -{ - import std.container : SList; - - assert(find("hello, world", "World").empty); - assert(find("hello, world", "wo") == "world"); - assert([1, 2, 3, 4].find(SList!int(2, 3)[]) == [2, 3, 4]); -} - -unittest -{ - import std.container : SList; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto lst = SList!int(1, 2, 5, 7, 3); - static assert(isForwardRange!(int[])); - static assert(isForwardRange!(typeof(lst[]))); - auto r = find(lst[], [2, 5]); - assert(equal(r, SList!int(2, 5, 7, 3)[])); -} - -// Specialization for searching a random-access range for a -// bidirectional range -R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) -if (isRandomAccessRange!R1 && isBidirectionalRange!R2 - && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) -{ - if (needle.empty) return haystack; - const needleLength = walkLength(needle.save); - if (needleLength > haystack.length) - { - // @@@BUG@@@ - //return haystack[$ .. $]; - return haystack[haystack.length .. haystack.length]; - } - // @@@BUG@@@ - // auto needleBack = moveBack(needle); - // Stage 1: find the step - size_t step = 1; - auto needleBack = needle.back; - needle.popBack(); - for (auto i = needle.save; !i.empty && !binaryFun!pred(i.back, needleBack); - i.popBack(), ++step) - { - } - // Stage 2: linear find - size_t scout = needleLength - 1; - for (;;) - { - if (scout >= haystack.length) - { - return haystack[haystack.length .. haystack.length]; - } - if (!binaryFun!pred(haystack[scout], needleBack)) - { - ++scout; - continue; - } - // Found a match with the last element in the needle - auto cand = haystack[scout + 1 - needleLength .. haystack.length]; - if (startsWith!pred(cand, needle)) - { - // found - return cand; - } - // Continue with the stride - scout += step; - } -} - -unittest -{ - //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); - // @@@BUG@@@ removing static below makes unittest fail - static struct BiRange - { - int[] payload; - @property bool empty() { return payload.empty; } - @property BiRange save() { return this; } - @property ref int front() { return payload[0]; } - @property ref int back() { return payload[$ - 1]; } - void popFront() { return payload.popFront(); } - void popBack() { return payload.popBack(); } - } - //static assert(isBidirectionalRange!BiRange); - auto r = BiRange([1, 2, 3, 10, 11, 4]); - //assert(equal(find(r, [3, 10]), BiRange([3, 10, 11, 4]))); - //assert(find("abc", "bc").length == 2); - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - //assert(find!"a == b"("abc", "bc").length == 2); -} - -// Leftover specialization: searching a random-access range for a -// non-bidirectional forward range -R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) -if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2 && - is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) -{ - static if (!is(ElementType!R1 == ElementType!R2)) - { - return simpleMindedFind!pred(haystack, needle); - } - else - { - // Prepare the search with needle's first element - if (needle.empty) - return haystack; - - haystack = .find!pred(haystack, needle.front); - - static if (hasLength!R1 && hasLength!R2 && is(typeof(takeNone(haystack)) == R1)) - { - if (needle.length > haystack.length) - return takeNone(haystack); - } - else - { - if (haystack.empty) - return haystack; - } - - needle.popFront(); - size_t matchLen = 1; - - // Loop invariant: haystack[0 .. matchLen] matches everything in - // the initial needle that was popped out of needle. - for (;;) - { - // Extend matchLength as much as possible - for (;;) - { - if (needle.empty || haystack.empty) - return haystack; - - static if (hasLength!R1 && is(typeof(takeNone(haystack)) == R1)) - { - if (matchLen == haystack.length) - return takeNone(haystack); - } - - if (!binaryFun!pred(haystack[matchLen], needle.front)) - break; - - ++matchLen; - needle.popFront(); - } - - auto bestMatch = haystack[0 .. matchLen]; - haystack.popFront(); - haystack = .find!pred(haystack, bestMatch); - } - } -} - -unittest -{ - import std.container : SList; - - assert(find([ 1, 2, 3 ], SList!int(2, 3)[]) == [ 2, 3 ]); - assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]); -} - -//Bug# 8334 -unittest -{ - auto haystack = [1, 2, 3, 4, 1, 9, 12, 42]; - auto needle = [12, 42, 27]; - - //different overload of find, but it's the base case. - assert(find(haystack, needle).empty); - - assert(find(haystack, takeExactly(filter!"true"(needle), 3)).empty); - assert(find(haystack, filter!"true"(needle)).empty); -} - -// Internally used by some find() overloads above. Can't make it -// private due to bugs in the compiler. -/*private*/ R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, R2 needle) -{ - enum estimateNeedleLength = hasLength!R1 && !hasLength!R2; - - static if (hasLength!R1) - { - static if (hasLength!R2) - size_t estimatedNeedleLength = 0; - else - immutable size_t estimatedNeedleLength = needle.length; - } - - bool haystackTooShort() - { - static if (estimateNeedleLength) - { - return haystack.length < estimatedNeedleLength; - } - else - { - return haystack.empty; - } - } - - searching: - for (;; haystack.popFront()) - { - if (haystackTooShort()) - { - // Failed search - static if (hasLength!R1) - { - static if (is(typeof(haystack[haystack.length .. - haystack.length]) : R1)) - return haystack[haystack.length .. haystack.length]; - else - return R1.init; - } - else - { - assert(haystack.empty); - return haystack; - } - } - static if (estimateNeedleLength) - size_t matchLength = 0; - for (auto h = haystack.save, n = needle.save; - !n.empty; - h.popFront(), n.popFront()) - { - if (h.empty || !binaryFun!pred(h.front, n.front)) - { - // Failed searching n in h - static if (estimateNeedleLength) - { - if (estimatedNeedleLength < matchLength) - estimatedNeedleLength = matchLength; - } - continue searching; - } - static if (estimateNeedleLength) - ++matchLength; - } - break; - } - return haystack; -} - -unittest -{ - // Test simpleMindedFind for the case where both haystack and needle have - // length. - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - struct CustomString - { - string _impl; - - // This is what triggers issue 7992. - @property size_t length() const { return _impl.length; } - @property void length(size_t len) { _impl.length = len; } - - // This is for conformance to the forward range API (we deliberately - // make it non-random access so that we will end up in - // simpleMindedFind). - @property bool empty() const { return _impl.empty; } - @property dchar front() const { return _impl.front; } - void popFront() { _impl.popFront(); } - @property CustomString save() { return this; } - } - - // If issue 7992 occurs, this will throw an exception from calling - // popFront() on an empty range. - auto r = find(CustomString("a"), CustomString("b")); -} - -/** -Finds two or more $(D needles) into a $(D haystack). The predicate $(D -pred) is used throughout to compare elements. By default, elements are -compared for equality. - -$(D BoyerMooreFinder) allocates GC memory. - -Params: - -haystack = The target of the search. Must be an $(GLOSSARY input -range). If any of $(D needles) is a range with elements comparable to -elements in $(D haystack), then $(D haystack) must be a $(GLOSSARY -forward range) such that the search can backtrack. - -needles = One or more items to search for. Each of $(D needles) must -be either comparable to one element in $(D haystack), or be itself a -$(GLOSSARY forward range) with elements comparable with elements in -$(D haystack). - -Returns: - -A tuple containing $(D haystack) positioned to match one of the -needles and also the 1-based index of the matching element in $(D -needles) (0 if none of $(D needles) matched, 1 if $(D needles[0]) -matched, 2 if $(D needles[1]) matched...). The first needle to be found -will be the one that matches. If multiple needles are found at the -same spot in the range, then the shortest one is the one which matches -(if multiple needles of the same length are found at the same spot (e.g -$(D "a") and $(D 'a')), then the left-most of them in the argument list -matches). - -The relationship between $(D haystack) and $(D needles) simply means -that one can e.g. search for individual $(D int)s or arrays of $(D -int)s in an array of $(D int)s. In addition, if elements are -individually comparable, searches of heterogeneous types are allowed -as well: a $(D double[]) can be searched for an $(D int) or a $(D -short[]), and conversely a $(D long) can be searched for a $(D float) -or a $(D double[]). This makes for efficient searches without the need -to coerce one side of the comparison into the other's side type. - -The complexity of the search is $(BIGOH haystack.length * -max(needles.length)). (For needles that are individual items, length -is considered to be 1.) The strategy used in searching several -subranges at once maximizes cache usage by moving in $(D haystack) as -few times as possible. - */ -Tuple!(Range, size_t) find(alias pred = "a == b", Range, Ranges...) -(Range haystack, Ranges needles) -if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles)))) -{ - for (;; haystack.popFront()) - { - size_t r = startsWith!pred(haystack, needles); - if (r || haystack.empty) - { - return tuple(haystack, r); - } - } -} - -/// -unittest -{ - int[] a = [ 1, 4, 2, 3 ]; - assert(find(a, 4) == [ 4, 2, 3 ]); - assert(find(a, [ 1, 4 ]) == [ 1, 4, 2, 3 ]); - assert(find(a, [ 1, 3 ], 4) == tuple([ 4, 2, 3 ], 2)); - // Mixed types allowed if comparable - assert(find(a, 5, [ 1.2, 3.5 ], 2.0) == tuple([ 2, 3 ], 3)); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto s1 = "Mary has a little lamb"; - //writeln(find(s1, "has a", "has an")); - assert(find(s1, "has a", "has an") == tuple("has a little lamb", 1)); - assert(find(s1, 't', "has a", "has an") == tuple("has a little lamb", 2)); - assert(find(s1, 't', "has a", 'y', "has an") == tuple("y has a little lamb", 3)); - assert(find("abc", "bc").length == 2); -} - -unittest -{ - import std.string : toUpper; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = [ 1, 2, 3 ]; - assert(find(a, 5).empty); - assert(find(a, 2) == [2, 3]); - - foreach (T; TypeTuple!(int, double)) - { - auto b = rndstuff!(T)(); - if (!b.length) continue; - b[$ / 2] = 200; - b[$ / 4] = 200; - assert(find(b, 200).length == b.length - b.length / 4); - } - - // Case-insensitive find of a string - string[] s = [ "Hello", "world", "!" ]; - //writeln(find!("toUpper(a) == toUpper(b)")(s, "hello")); - assert(find!("toUpper(a) == toUpper(b)")(s, "hello").length == 3); - - static bool f(string a, string b) { return toUpper(a) == toUpper(b); } - assert(find!(f)(s, "hello").length == 3); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = [ 1, 2, 3, 2, 6 ]; - assert(find(std.range.retro(a), 5).empty); - assert(equal(find(std.range.retro(a), 2), [ 2, 3, 2, 1 ][])); - - foreach (T; TypeTuple!(int, double)) - { - auto b = rndstuff!(T)(); - if (!b.length) continue; - b[$ / 2] = 200; - b[$ / 4] = 200; - assert(find(std.range.retro(b), 200).length == - b.length - (b.length - 1) / 2); - } -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; - int[] b = [ 1, 2, 3 ]; - assert(find(a, b) == [ 1, 2, 3, 4, 5 ]); - assert(find(b, a).empty); - - foreach (DummyType; AllDummyRanges) { - DummyType d; - auto findRes = find(d, 5); - assert(equal(findRes, [5,6,7,8,9,10])); - } -} - -/// Ditto -struct BoyerMooreFinder(alias pred, Range) -{ -private: - size_t[] skip; // GC allocated - ptrdiff_t[ElementType!(Range)] occ; // GC allocated - Range needle; - - ptrdiff_t occurrence(ElementType!(Range) c) - { - auto p = c in occ; - return p ? *p : -1; - } - -/* -This helper function checks whether the last "portion" bytes of -"needle" (which is "nlen" bytes long) exist within the "needle" at -offset "offset" (counted from the end of the string), and whether the -character preceding "offset" is not a match. Notice that the range -being checked may reach beyond the beginning of the string. Such range -is ignored. - */ - static bool needlematch(R)(R needle, - size_t portion, size_t offset) - { - ptrdiff_t virtual_begin = needle.length - offset - portion; - ptrdiff_t ignore = 0; - if (virtual_begin < 0) { - ignore = -virtual_begin; - virtual_begin = 0; - } - if (virtual_begin > 0 - && needle[virtual_begin - 1] == needle[$ - portion - 1]) - return 0; - - immutable delta = portion - ignore; - return equal(needle[needle.length - delta .. needle.length], - needle[virtual_begin .. virtual_begin + delta]); - } - -public: - this(Range needle) - { - if (!needle.length) return; - this.needle = needle; - /* Populate table with the analysis of the needle */ - /* But ignoring the last letter */ - foreach (i, n ; needle[0 .. $ - 1]) - { - this.occ[n] = i; - } - /* Preprocess #2: init skip[] */ - /* Note: This step could be made a lot faster. - * A simple implementation is shown here. */ - this.skip = new size_t[needle.length]; - foreach (a; 0 .. needle.length) - { - size_t value = 0; - while (value < needle.length - && !needlematch(needle, a, value)) - { - ++value; - } - this.skip[needle.length - a - 1] = value; - } - } - - Range beFound(Range haystack) - { - if (!needle.length) return haystack; - if (needle.length > haystack.length) return haystack[$ .. $]; - /* Search: */ - auto limit = haystack.length - needle.length; - for (size_t hpos = 0; hpos <= limit; ) - { - size_t npos = needle.length - 1; - while (pred(needle[npos], haystack[npos+hpos])) - { - if (npos == 0) return haystack[hpos .. $]; - --npos; - } - hpos += max(skip[npos], cast(sizediff_t) npos - occurrence(haystack[npos+hpos])); - } - return haystack[$ .. $]; - } - - @property size_t length() - { - return needle.length; - } - - alias opDollar = length; -} - -/// Ditto -BoyerMooreFinder!(binaryFun!(pred), Range) boyerMooreFinder -(alias pred = "a == b", Range) -(Range needle) if (isRandomAccessRange!(Range) || isSomeString!Range) -{ - return typeof(return)(needle); -} - -// Oddly this is not disabled by bug 4759 -Range1 find(Range1, alias pred, Range2)( - Range1 haystack, BoyerMooreFinder!(pred, Range2) needle) -{ - return needle.beFound(haystack); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - string h = "/homes/aalexand/d/dmd/bin/../lib/libphobos.a(dmain2.o)"~ - "(.gnu.linkonce.tmain+0x74): In function `main' undefined reference"~ - " to `_Dmain':"; - string[] ns = ["libphobos", "function", " undefined", "`", ":"]; - foreach (n ; ns) { - auto p = find(h, boyerMooreFinder(n)); - assert(!p.empty); - } - - int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; - int[] b = [ 1, 2, 3 ]; - //writeln(find(a, boyerMooreFinder(b))); - assert(find(a, boyerMooreFinder(b)) == [ 1, 2, 3, 4, 5 ]); - assert(find(b, boyerMooreFinder(a)).empty); -} - -unittest -{ - auto bm = boyerMooreFinder("for"); - auto match = find("Moor", bm); - assert(match.empty); -} - -/** -Advances the input range $(D haystack) by calling $(D haystack.popFront) -until either $(D pred(haystack.front)), or $(D -haystack.empty). Performs $(BIGOH haystack.length) evaluations of $(D -pred). - -To find the last element of a bidirectional $(D haystack) satisfying -$(D pred), call $(D find!(pred)(retro(haystack))). See $(XREF -range, retro). - -See_Also: - $(WEB sgi.com/tech/stl/find_if.html, STL's find_if) -*/ -InputRange find(alias pred, InputRange)(InputRange haystack) -if (isInputRange!InputRange) -{ - alias R = InputRange; - alias predFun = unaryFun!pred; - static if (isNarrowString!R) - { - import std.utf : decode; - - immutable len = haystack.length; - size_t i = 0, next = 0; - while (next < len) - { - if (predFun(decode(haystack, next))) - return haystack[i .. $]; - i = next; - } - return haystack[$ .. $]; - } - else static if (!isInfinite!R && hasSlicing!R && is(typeof(haystack[cast(size_t)0 .. $]))) - { - size_t i = 0; - foreach (ref e; haystack) - { - if (predFun(e)) - return haystack[i .. $]; - ++i; - } - return haystack[$ .. $]; - } - else - { - //standard range - for ( ; !haystack.empty; haystack.popFront() ) - { - if (predFun(haystack.front)) - break; - } - return haystack; - } -} - -/// -unittest -{ - auto arr = [ 1, 2, 3, 4, 1 ]; - assert(find!("a > 2")(arr) == [ 3, 4, 1 ]); - - // with predicate alias - bool pred(int x) { return x + 1 > 1.5; } - assert(find!(pred)(arr) == arr); -} - -@safe pure unittest -{ - //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] r = [ 1, 2, 3 ]; - assert(find!(a=>a > 2)(r) == [3]); - bool pred(int x) { return x + 1 > 1.5; } - assert(find!(pred)(r) == r); - - assert(find!(a=>a > 'v')("hello world") == "world"); - assert(find!(a=>a%4 == 0)("日本語") == "本語"); -} - -// findSkip -/** - * If $(D needle) occurs in $(D haystack), positions $(D haystack) - * right after the first occurrence of $(D needle) and returns $(D - * true). Otherwise, leaves $(D haystack) as is and returns $(D - * false). - */ -bool findSkip(alias pred = "a == b", R1, R2)(ref R1 haystack, R2 needle) -if (isForwardRange!R1 && isForwardRange!R2 - && is(typeof(binaryFun!pred(haystack.front, needle.front)))) -{ - auto parts = findSplit!pred(haystack, needle); - if (parts[1].empty) return false; - // found - haystack = parts[2]; - return true; -} - -/// -unittest -{ - string s = "abcdef"; - assert(findSkip(s, "cd") && s == "ef"); - s = "abcdef"; - assert(!findSkip(s, "cxd") && s == "abcdef"); - s = "abcdef"; - assert(findSkip(s, "def") && s.empty); -} - -/** -These functions find the first occurrence of $(D needle) in $(D -haystack) and then split $(D haystack) as follows. - -$(D findSplit) returns a tuple $(D result) containing $(I three) -ranges. $(D result[0]) is the portion of $(D haystack) before $(D -needle), $(D result[1]) is the portion of $(D haystack) that matches -$(D needle), and $(D result[2]) is the portion of $(D haystack) after -the match. If $(D needle) was not found, $(D result[0]) -comprehends $(D haystack) entirely and $(D result[1]) and $(D result[2]) -are empty. - -$(D findSplitBefore) returns a tuple $(D result) containing two -ranges. $(D result[0]) is the portion of $(D haystack) before $(D -needle), and $(D result[1]) is the balance of $(D haystack) starting -with the match. If $(D needle) was not found, $(D result[0]) -comprehends $(D haystack) entirely and $(D result[1]) is empty. - -$(D findSplitAfter) returns a tuple $(D result) containing two ranges. -$(D result[0]) is the portion of $(D haystack) up to and including the -match, and $(D result[1]) is the balance of $(D haystack) starting -after the match. If $(D needle) was not found, $(D result[0]) is empty -and $(D result[1]) is $(D haystack). - -In all cases, the concatenation of the returned ranges spans the -entire $(D haystack). - -If $(D haystack) is a random-access range, all three components of the -tuple have the same type as $(D haystack). Otherwise, $(D haystack) -must be a forward range and the type of $(D result[0]) and $(D -result[1]) is the same as $(XREF range,takeExactly). - */ -auto findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) -if (isForwardRange!R1 && isForwardRange!R2) -{ - static if (isSomeString!R1 && isSomeString!R2 - || isRandomAccessRange!R1 && hasLength!R2) - { - auto balance = find!pred(haystack, needle); - immutable pos1 = haystack.length - balance.length; - immutable pos2 = balance.empty ? pos1 : pos1 + needle.length; - return tuple(haystack[0 .. pos1], - haystack[pos1 .. pos2], - haystack[pos2 .. haystack.length]); - } - else - { - auto original = haystack.save; - auto h = haystack.save; - auto n = needle.save; - size_t pos1, pos2; - while (!n.empty && !h.empty) - { - if (binaryFun!pred(h.front, n.front)) - { - h.popFront(); - n.popFront(); - ++pos2; - } - else - { - haystack.popFront(); - n = needle.save; - h = haystack.save; - pos2 = ++pos1; - } - } - return tuple(takeExactly(original, pos1), - takeExactly(haystack, pos2 - pos1), - h); - } -} - -/// Ditto -auto findSplitBefore(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) -if (isForwardRange!R1 && isForwardRange!R2) -{ - static if (isSomeString!R1 && isSomeString!R2 - || isRandomAccessRange!R1 && hasLength!R2) - { - auto balance = find!pred(haystack, needle); - immutable pos = haystack.length - balance.length; - return tuple(haystack[0 .. pos], haystack[pos .. haystack.length]); - } - else - { - auto original = haystack.save; - auto h = haystack.save; - auto n = needle.save; - size_t pos; - while (!n.empty && !h.empty) - { - if (binaryFun!pred(h.front, n.front)) - { - h.popFront(); - n.popFront(); - } - else - { - haystack.popFront(); - n = needle.save; - h = haystack.save; - ++pos; - } - } - return tuple(takeExactly(original, pos), haystack); - } -} - -/// Ditto -auto findSplitAfter(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) -if (isForwardRange!R1 && isForwardRange!R2) -{ - static if (isSomeString!R1 && isSomeString!R2 - || isRandomAccessRange!R1 && hasLength!R2) - { - auto balance = find!pred(haystack, needle); - immutable pos = balance.empty ? 0 : haystack.length - balance.length + needle.length; - return tuple(haystack[0 .. pos], haystack[pos .. haystack.length]); - } - else - { - auto original = haystack.save; - auto h = haystack.save; - auto n = needle.save; - size_t pos1, pos2; - while (!n.empty) - { - if (h.empty) - { - // Failed search - return tuple(takeExactly(original, 0), original); - } - if (binaryFun!pred(h.front, n.front)) - { - h.popFront(); - n.popFront(); - ++pos2; - } - else - { - haystack.popFront(); - n = needle.save; - h = haystack.save; - pos2 = ++pos1; - } - } - return tuple(takeExactly(original, pos2), h); - } -} - -/// -unittest -{ - auto a = "Carl Sagan Memorial Station"; - auto r = findSplit(a, "Velikovsky"); - assert(r[0] == a); - assert(r[1].empty); - assert(r[2].empty); - r = findSplit(a, " "); - assert(r[0] == "Carl"); - assert(r[1] == " "); - assert(r[2] == "Sagan Memorial Station"); - auto r1 = findSplitBefore(a, "Sagan"); - assert(r1[0] == "Carl ", r1[0]); - assert(r1[1] == "Sagan Memorial Station"); - auto r2 = findSplitAfter(a, "Sagan"); - assert(r2[0] == "Carl Sagan"); - assert(r2[1] == " Memorial Station"); -} - -unittest -{ - auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; - auto r = findSplit(a, [9, 1]); - assert(r[0] == a); - assert(r[1].empty); - assert(r[2].empty); - r = findSplit(a, [3]); - assert(r[0] == a[0 .. 2]); - assert(r[1] == a[2 .. 3]); - assert(r[2] == a[3 .. $]); - - auto r1 = findSplitBefore(a, [9, 1]); - assert(r1[0] == a); - assert(r1[1].empty); - r1 = findSplitBefore(a, [3, 4]); - assert(r1[0] == a[0 .. 2]); - assert(r1[1] == a[2 .. $]); - - r1 = findSplitAfter(a, [9, 1]); - assert(r1[0].empty); - assert(r1[1] == a); - r1 = findSplitAfter(a, [3, 4]); - assert(r1[0] == a[0 .. 4]); - assert(r1[1] == a[4 .. $]); -} - -unittest -{ - auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; - auto fwd = filter!"a > 0"(a); - auto r = findSplit(fwd, [9, 1]); - assert(equal(r[0], a)); - assert(r[1].empty); - assert(r[2].empty); - r = findSplit(fwd, [3]); - assert(equal(r[0], a[0 .. 2])); - assert(equal(r[1], a[2 .. 3])); - assert(equal(r[2], a[3 .. $])); - - auto r1 = findSplitBefore(fwd, [9, 1]); - assert(equal(r1[0], a)); - assert(r1[1].empty); - r1 = findSplitBefore(fwd, [3, 4]); - assert(equal(r1[0], a[0 .. 2])); - assert(equal(r1[1], a[2 .. $])); - - r1 = findSplitAfter(fwd, [9, 1]); - assert(r1[0].empty); - assert(equal(r1[1], a)); - r1 = findSplitAfter(fwd, [3, 4]); - assert(equal(r1[0], a[0 .. 4])); - assert(equal(r1[1], a[4 .. $])); -} - -/++ - Returns the number of elements which must be popped from the front of - $(D haystack) before reaching an element for which - $(D startsWith!pred(haystack, needles)) is $(D true). If - $(D startsWith!pred(haystack, needles)) is not $(D true) for any element in - $(D haystack), then $(D -1) is returned. - - $(D needles) may be either an element or a range. - +/ -ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) - if (isForwardRange!R - && Rs.length > 0 - && isForwardRange!(Rs[0]) == isInputRange!(Rs[0]) - && is(typeof(startsWith!pred(haystack, needles[0]))) - && (Rs.length == 1 - || is(typeof(countUntil!pred(haystack, needles[1 .. $]))))) -{ - typeof(return) result; - - static if (needles.length == 1) - { - static if (hasLength!R) //Note: Narrow strings don't have length. - { - //We delegate to find because find is very efficient. - //We store the length of the haystack so we don't have to save it. - auto len = haystack.length; - auto r2 = find!pred(haystack, needles[0]); - if (!r2.empty) - return cast(typeof(return)) (len - r2.length); - } - else - { - if (needles[0].empty) - return 0; - - //Default case, slower route doing startsWith iteration - for ( ; !haystack.empty ; ++result ) - { - //We compare the first elements of the ranges here before - //forwarding to startsWith. This avoids making useless saves to - //haystack/needle if they aren't even going to be mutated anyways. - //It also cuts down on the amount of pops on haystack. - if (binaryFun!pred(haystack.front, needles[0].front)) - { - //Here, we need to save the needle before popping it. - //haystack we pop in all paths, so we do that, and then save. - haystack.popFront(); - if (startsWith!pred(haystack.save, needles[0].save.dropOne())) - return result; - } - else - haystack.popFront(); - } - } - } - else - { - foreach (i, Ri; Rs) - { - static if (isForwardRange!Ri) - { - if (needles[i].empty) - return 0; - } - } - Tuple!Rs t; - foreach (i, Ri; Rs) - { - static if (!isForwardRange!Ri) - { - t[i] = needles[i]; - } - } - for (; !haystack.empty ; ++result, haystack.popFront()) - { - foreach (i, Ri; Rs) - { - static if (isForwardRange!Ri) - { - t[i] = needles[i].save; - } - } - if (startsWith!pred(haystack.save, t.expand)) - { - return result; - } - } - } - - //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" - static if (isInfinite!R) assert(0); - else return -1; -} - -/// ditto -ptrdiff_t countUntil(alias pred = "a == b", R, N)(R haystack, N needle) - if (isInputRange!R && - is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) -{ - bool pred2(ElementType!R a) { return binaryFun!pred(a, needle); } - return countUntil!pred2(haystack); -} - -/// -unittest -{ - assert(countUntil("hello world", "world") == 6); - assert(countUntil("hello world", 'r') == 8); - assert(countUntil("hello world", "programming") == -1); - assert(countUntil("日本語", "本語") == 1); - assert(countUntil("日本語", '語') == 2); - assert(countUntil("日本語", "五") == -1); - assert(countUntil("日本語", '五') == -1); - assert(countUntil([0, 7, 12, 22, 9], [12, 22]) == 2); - assert(countUntil([0, 7, 12, 22, 9], 9) == 4); - assert(countUntil!"a > b"([0, 7, 12, 22, 9], 20) == 3); -} - -unittest -{ - assert(countUntil("日本語", "") == 0); - assert(countUntil("日本語"d, "") == 0); - - assert(countUntil("", "") == 0); - assert(countUntil("".filter!"true"(), "") == 0); - - auto rf = [0, 20, 12, 22, 9].filter!"true"(); - assert(rf.countUntil!"a > b"((int[]).init) == 0); - assert(rf.countUntil!"a > b"(20) == 3); - assert(rf.countUntil!"a > b"([20, 8]) == 3); - assert(rf.countUntil!"a > b"([20, 10]) == -1); - assert(rf.countUntil!"a > b"([20, 8, 0]) == -1); - - auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]); - auto r2 = new ReferenceForwardRange!int([3, 4]); - auto r3 = new ReferenceForwardRange!int([3, 5]); - assert(r.save.countUntil(3) == 3); - assert(r.save.countUntil(r2) == 3); - assert(r.save.countUntil(7) == -1); - assert(r.save.countUntil(r3) == -1); -} - -unittest -{ - assert(countUntil("hello world", "world", "asd") == 6); - assert(countUntil("hello world", "world", "ello") == 1); - assert(countUntil("hello world", "world", "") == 0); - assert(countUntil("hello world", "world", 'l') == 2); -} - -/++ - Returns the number of elements which must be popped from $(D haystack) - before $(D pred(haystack.front)) is $(D true). - +/ -ptrdiff_t countUntil(alias pred, R)(R haystack) - if (isInputRange!R && - is(typeof(unaryFun!pred(haystack.front)) : bool)) -{ - typeof(return) i; - static if (isRandomAccessRange!R) - { - //Optimized RA implementation. Since we want to count *and* iterate at - //the same time, it is more efficient this way. - static if (hasLength!R) - { - immutable len = cast(typeof(return)) haystack.length; - for ( ; i < len ; ++i ) - if (unaryFun!pred(haystack[i])) return i; - } - else //if (isInfinite!R) - { - for ( ; ; ++i ) - if (unaryFun!pred(haystack[i])) return i; - } - } - else static if (hasLength!R) - { - //For those odd ranges that have a length, but aren't RA. - //It is faster to quick find, and then compare the lengths - auto r2 = find!pred(haystack.save); - if (!r2.empty) return cast(typeof(return)) (haystack.length - r2.length); - } - else //Everything else - { - alias T = ElementType!R; //For narrow strings forces dchar iteration - foreach (T elem; haystack) - { - if (unaryFun!pred(elem)) return i; - ++i; - } - } - - //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" - static if (isInfinite!R) assert(0); - else return -1; -} - -/// -unittest -{ - import std.ascii : isDigit; - import std.uni : isWhite; - - assert(countUntil!(std.uni.isWhite)("hello world") == 5); - assert(countUntil!(std.ascii.isDigit)("hello world") == -1); - assert(countUntil!"a > 20"([0, 7, 12, 22, 9]) == 3); -} - -unittest -{ - // References - { - // input - ReferenceInputRange!int r; - r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]); - assert(r.countUntil(3) == 3); - r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]); - assert(r.countUntil(7) == -1); - } - { - // forward - auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]); - assert(r.save.countUntil([3, 4]) == 3); - assert(r.save.countUntil(3) == 3); - assert(r.save.countUntil([3, 7]) == -1); - assert(r.save.countUntil(7) == -1); - } - { - // infinite forward - auto r = new ReferenceInfiniteForwardRange!int(0); - assert(r.save.countUntil([3, 4]) == 3); - assert(r.save.countUntil(3) == 3); - } -} - -/** -Interval option specifier for $(D until) (below) and others. - */ -enum OpenRight -{ - no, /// Interval is closed to the right (last element included) - yes /// Interval is open to the right (last element is not included) -} - -/** -Lazily iterates $(D range) until value $(D sentinel) is found, at -which point it stops. - */ -struct Until(alias pred, Range, Sentinel) if (isInputRange!Range) -{ - private Range _input; - static if (!is(Sentinel == void)) - private Sentinel _sentinel; - // mixin(bitfields!( - // OpenRight, "_openRight", 1, - // bool, "_done", 1, - // uint, "", 6)); - // OpenRight, "_openRight", 1, - // bool, "_done", 1, - OpenRight _openRight; - bool _done; - - static if (!is(Sentinel == void)) - this(Range input, Sentinel sentinel, - OpenRight openRight = OpenRight.yes) - { - _input = input; - _sentinel = sentinel; - _openRight = openRight; - _done = _input.empty || openRight && predSatisfied(); - } - else - this(Range input, OpenRight openRight = OpenRight.yes) - { - _input = input; - _openRight = openRight; - _done = _input.empty || openRight && predSatisfied(); - } - - @property bool empty() - { - return _done; - } - - @property auto ref front() - { - assert(!empty); - return _input.front; - } - - private bool predSatisfied() - { - static if (is(Sentinel == void)) - return unaryFun!pred(_input.front); - else - return startsWith!pred(_input, _sentinel); - } - - void popFront() - { - assert(!empty); - if (!_openRight) - { - _done = predSatisfied(); - _input.popFront(); - _done = _done || _input.empty; - } - else - { - _input.popFront(); - _done = _input.empty || predSatisfied(); - } - } - - static if (isForwardRange!Range) - { - static if (!is(Sentinel == void)) - @property Until save() - { - Until result = this; - result._input = _input.save; - result._sentinel = _sentinel; - result._openRight = _openRight; - result._done = _done; - return result; - } - else - @property Until save() - { - Until result = this; - result._input = _input.save; - result._openRight = _openRight; - result._done = _done; - return result; - } - } -} - -/// Ditto -Until!(pred, Range, Sentinel) -until(alias pred = "a == b", Range, Sentinel) -(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes) -if (!is(Sentinel == OpenRight)) -{ - return typeof(return)(range, sentinel, openRight); -} - -/// Ditto -Until!(pred, Range, void) -until(alias pred, Range) -(Range range, OpenRight openRight = OpenRight.yes) -{ - return typeof(return)(range, openRight); -} - -/// -unittest -{ - int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; - assert(equal(a.until(7), [1, 2, 4][])); - assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][])); -} - -unittest -{ - //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; - - static assert(isForwardRange!(typeof(a.until(7)))); - static assert(isForwardRange!(typeof(until!"a == 2"(a, OpenRight.no)))); - - assert(equal(a.until(7), [1, 2, 4][])); - assert(equal(a.until([7, 2]), [1, 2, 4, 7][])); - assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][])); - assert(equal(until!"a == 2"(a, OpenRight.no), [1, 2][])); -} - -unittest // bugzilla 13171 -{ - auto a = [1, 2, 3, 4]; - assert(equal(refRange(&a).until(3, OpenRight.no), [1, 2, 3])); - assert(a == [4]); -} - -unittest // Issue 10460 -{ - auto a = [1, 2, 3, 4]; - foreach (ref e; a.until(3)) - e = 0; - assert(equal(a, [0, 0, 3, 4])); -} - -/** -If the range $(D doesThisStart) starts with $(I any) of the $(D -withOneOfThese) ranges or elements, returns 1 if it starts with $(D -withOneOfThese[0]), 2 if it starts with $(D withOneOfThese[1]), and so -on. If none match, returns 0. In the case where $(D doesThisStart) starts -with multiple of the ranges or elements in $(D withOneOfThese), then the -shortest one matches (if there are two which match which are of the same -length (e.g. $(D "a") and $(D 'a')), then the left-most of them in the argument -list matches). - */ -uint startsWith(alias pred = "a == b", Range, Needles...)(Range doesThisStart, Needles withOneOfThese) -if (isInputRange!Range && Needles.length > 1 && - is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) : bool ) && - is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $])) : uint)) -{ - alias haystack = doesThisStart; - alias needles = withOneOfThese; - - // Make one pass looking for empty ranges in needles - foreach (i, Unused; Needles) - { - // Empty range matches everything - static if (!is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) - { - if (needles[i].empty) return i + 1; - } - } - - for (; !haystack.empty; haystack.popFront()) - { - foreach (i, Unused; Needles) - { - static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) - { - // Single-element - if (binaryFun!pred(haystack.front, needles[i])) - { - // found, but instead of returning, we just stop searching. - // This is to account for one-element - // range matches (consider startsWith("ab", "a", - // 'a') should return 1, not 2). - break; - } - } - else - { - if (binaryFun!pred(haystack.front, needles[i].front)) - { - continue; - } - } - - // This code executed on failure to match - // Out with this guy, check for the others - uint result = startsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]); - if (result > i) ++result; - return result; - } - - // If execution reaches this point, then the front matches for all - // needle ranges, or a needle element has been matched. - // What we need to do now is iterate, lopping off the front of - // the range and checking if the result is empty, or finding an - // element needle and returning. - // If neither happens, we drop to the end and loop. - foreach (i, Unused; Needles) - { - static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) - { - // Test has passed in the previous loop - return i + 1; - } - else - { - needles[i].popFront(); - if (needles[i].empty) return i + 1; - } - } - } - return 0; -} - -/// Ditto -bool startsWith(alias pred = "a == b", R1, R2)(R1 doesThisStart, R2 withThis) -if (isInputRange!R1 && - isInputRange!R2 && - is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) : bool)) -{ - alias haystack = doesThisStart; - alias needle = withThis; - - static if (is(typeof(pred) : string)) - enum isDefaultPred = pred == "a == b"; - else - enum isDefaultPred = false; - - //Note: While narrow strings don't have a "true" length, for a narrow string to start with another - //narrow string *of the same type*, it must have *at least* as many code units. - static if ((hasLength!R1 && hasLength!R2) || - (isNarrowString!R1 && isNarrowString!R2 && ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof)) - { - if (haystack.length < needle.length) - return false; - } - - static if (isDefaultPred && isArray!R1 && isArray!R2 && - is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) - { - //Array slice comparison mode - return haystack[0 .. needle.length] == needle; - } - else static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && hasLength!R2) - { - //RA dual indexing mode - foreach (j; 0 .. needle.length) - { - if (!binaryFun!pred(needle[j], haystack[j])) - // not found - return false; - } - // found! - return true; - } - else - { - //Standard input range mode - if (needle.empty) return true; - static if (hasLength!R1 && hasLength!R2) - { - //We have previously checked that haystack.length > needle.length, - //So no need to check haystack.empty during iteration - for ( ; ; haystack.popFront() ) - { - if (!binaryFun!pred(haystack.front, needle.front)) break; - needle.popFront(); - if (needle.empty) return true; - } - } - else - { - for ( ; !haystack.empty ; haystack.popFront() ) - { - if (!binaryFun!pred(haystack.front, needle.front)) break; - needle.popFront(); - if (needle.empty) return true; - } - } - return false; - } -} - -/// Ditto -bool startsWith(alias pred = "a == b", R, E)(R doesThisStart, E withThis) -if (isInputRange!R && - is(typeof(binaryFun!pred(doesThisStart.front, withThis)) : bool)) -{ - return doesThisStart.empty - ? false - : binaryFun!pred(doesThisStart.front, withThis); -} - -/// -unittest -{ - assert(startsWith("abc", "")); - assert(startsWith("abc", "a")); - assert(!startsWith("abc", "b")); - assert(startsWith("abc", 'a', "b") == 1); - assert(startsWith("abc", "b", "a") == 2); - assert(startsWith("abc", "a", "a") == 1); - assert(startsWith("abc", "ab", "a") == 2); - assert(startsWith("abc", "x", "a", "b") == 2); - assert(startsWith("abc", "x", "aa", "ab") == 3); - assert(startsWith("abc", "x", "aaa", "sab") == 0); - assert(startsWith("abc", "x", "aaa", "a", "sab") == 3); -} - -unittest -{ - import std.conv : to; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) - { - assert(!startsWith(to!S("abc"), 'c')); - assert(startsWith(to!S("abc"), 'a', 'c') == 1); - assert(!startsWith(to!S("abc"), 'x', 'n', 'b')); - assert(startsWith(to!S("abc"), 'x', 'n', 'a') == 3); - assert(startsWith(to!S("\uFF28abc"), 'a', '\uFF28', 'c') == 2); - - foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) - { - //Lots of strings - assert(startsWith(to!S("abc"), to!T(""))); - assert(startsWith(to!S("ab"), to!T("a"))); - assert(startsWith(to!S("abc"), to!T("a"))); - assert(!startsWith(to!S("abc"), to!T("b"))); - assert(!startsWith(to!S("abc"), to!T("b"), "bc", "abcd", "xyz")); - assert(startsWith(to!S("abc"), to!T("ab"), 'a') == 2); - assert(startsWith(to!S("abc"), to!T("a"), "b") == 1); - assert(startsWith(to!S("abc"), to!T("b"), "a") == 2); - assert(startsWith(to!S("abc"), to!T("a"), 'a') == 1); - assert(startsWith(to!S("abc"), 'a', to!T("a")) == 1); - assert(startsWith(to!S("abc"), to!T("x"), "a", "b") == 2); - assert(startsWith(to!S("abc"), to!T("x"), "aa", "ab") == 3); - assert(startsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0); - assert(startsWith(to!S("abc"), 'a')); - assert(!startsWith(to!S("abc"), to!T("sab"))); - assert(startsWith(to!S("abc"), 'x', to!T("aaa"), 'a', "sab") == 3); - - //Unicode - assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("\uFF28el"))); - assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("Hel"), to!T("\uFF28el")) == 2); - assert(startsWith(to!S("日本語"), to!T("日本"))); - assert(startsWith(to!S("日本語"), to!T("日本語"))); - assert(!startsWith(to!S("日本"), to!T("日本語"))); - - //Empty - assert(startsWith(to!S(""), T.init)); - assert(!startsWith(to!S(""), 'a')); - assert(startsWith(to!S("a"), T.init)); - assert(startsWith(to!S("a"), T.init, "") == 1); - assert(startsWith(to!S("a"), T.init, 'a') == 1); - assert(startsWith(to!S("a"), 'a', T.init) == 2); - } - } - - //Length but no RA - assert(!startsWith("abc".takeExactly(3), "abcd".takeExactly(4))); - assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(3))); - assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(1))); - - foreach (T; TypeTuple!(int, short)) - { - immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; - - //RA range - assert(startsWith(arr, cast(int[])null)); - assert(!startsWith(arr, 5)); - assert(!startsWith(arr, 1)); - assert(startsWith(arr, 0)); - assert(startsWith(arr, 5, 0, 1) == 2); - assert(startsWith(arr, [0])); - assert(startsWith(arr, [0, 1])); - assert(startsWith(arr, [0, 1], 7) == 1); - assert(!startsWith(arr, [0, 1, 7])); - assert(startsWith(arr, [0, 1, 7], [0, 1, 2]) == 2); - - //Normal input range - assert(!startsWith(filter!"true"(arr), 1)); - assert(startsWith(filter!"true"(arr), 0)); - assert(startsWith(filter!"true"(arr), [0])); - assert(startsWith(filter!"true"(arr), [0, 1])); - assert(startsWith(filter!"true"(arr), [0, 1], 7) == 1); - assert(!startsWith(filter!"true"(arr), [0, 1, 7])); - assert(startsWith(filter!"true"(arr), [0, 1, 7], [0, 1, 2]) == 2); - assert(startsWith(arr, filter!"true"([0, 1]))); - assert(startsWith(arr, filter!"true"([0, 1]), 7) == 1); - assert(!startsWith(arr, filter!"true"([0, 1, 7]))); - assert(startsWith(arr, [0, 1, 7], filter!"true"([0, 1, 2])) == 2); - - //Non-default pred - assert(startsWith!("a%10 == b%10")(arr, [10, 11])); - assert(!startsWith!("a%10 == b%10")(arr, [10, 12])); - } -} - -/** -If $(D startsWith(r1, r2)), consume the corresponding elements off $(D -r1) and return $(D true). Otherwise, leave $(D r1) unchanged and -return $(D false). - */ -bool skipOver(alias pred = "a == b", R1, R2)(ref R1 r1, R2 r2) -if (is(typeof(binaryFun!pred(r1.front, r2.front)))) -{ - auto r = r1.save; - while (!r2.empty && !r.empty && binaryFun!pred(r.front, r2.front)) - { - r.popFront(); - r2.popFront(); - } - if (r2.empty) - r1 = r; - return r2.empty; -} - -/// -unittest -{ - auto s1 = "Hello world"; - assert(!skipOver(s1, "Ha")); - assert(s1 == "Hello world"); - assert(skipOver(s1, "Hell") && s1 == "o world"); - - string[] r1 = ["abc", "def", "hij"]; - dstring[] r2 = ["abc"d]; - assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d])); - assert(r1 == ["abc", "def", "hij"]); - assert(skipOver!((a, b) => a.equal(b))(r1, r2)); - assert(r1 == ["def", "hij"]); -} - -/** -Checks whether a range starts with an element, and if so, consume that -element off $(D r) and return $(D true). Otherwise, leave $(D r) -unchanged and return $(D false). - */ -bool skipOver(alias pred = "a == b", R, E)(ref R r, E e) -if (is(typeof(binaryFun!pred(r.front, e)))) -{ - if (r.empty || !binaryFun!pred(r.front, e)) - return false; - r.popFront(); - return true; -} - -/// -unittest { - auto s1 = "Hello world"; - assert(!skipOver(s1, 'a')); - assert(s1 == "Hello world"); - assert(skipOver(s1, 'H') && s1 == "ello world"); - - string[] r = ["abc", "def", "hij"]; - dstring e = "abc"d; - assert(!skipOver!((a, b) => a.equal(b))(r, "def"d)); - assert(r == ["abc", "def", "hij"]); - assert(skipOver!((a, b) => a.equal(b))(r, e)); - assert(r == ["def", "hij"]); - - auto s2 = ""; - assert(!s2.skipOver('a')); -} - -/* (Not yet documented.) -Consume all elements from $(D r) that are equal to one of the elements -$(D es). - */ -void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es) -//if (is(typeof(binaryFun!pred(r1.front, es[0])))) -{ - loop: - for (; !r.empty; r.popFront()) - { - foreach (i, E; Es) - { - if (binaryFun!pred(r.front, es[i])) - { - continue loop; - } - } - break; - } -} - -unittest -{ - //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto s1 = "Hello world"; - skipAll(s1, 'H', 'e'); - assert(s1 == "llo world"); -} - -/** -The reciprocal of $(D startsWith). - */ -uint endsWith(alias pred = "a == b", Range, Needles...)(Range doesThisEnd, Needles withOneOfThese) -if (isBidirectionalRange!Range && Needles.length > 1 && - is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[0])) : bool) && - is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[1 .. $])) : uint)) -{ - alias haystack = doesThisEnd; - alias needles = withOneOfThese; - - // Make one pass looking for empty ranges in needles - foreach (i, Unused; Needles) - { - // Empty range matches everything - static if (!is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) - { - if (needles[i].empty) return i + 1; - } - } - - for (; !haystack.empty; haystack.popBack()) - { - foreach (i, Unused; Needles) - { - static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) - { - // Single-element - if (binaryFun!pred(haystack.back, needles[i])) - { - // found, but continue to account for one-element - // range matches (consider endsWith("ab", "b", - // 'b') should return 1, not 2). - continue; - } - } - else - { - if (binaryFun!pred(haystack.back, needles[i].back)) - continue; - } - - // This code executed on failure to match - // Out with this guy, check for the others - uint result = endsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]); - if (result > i) ++result; - return result; - } - - // If execution reaches this point, then the back matches for all - // needles ranges. What we need to do now is to lop off the back of - // all ranges involved and recurse. - foreach (i, Unused; Needles) - { - static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) - { - // Test has passed in the previous loop - return i + 1; - } - else - { - needles[i].popBack(); - if (needles[i].empty) return i + 1; - } - } - } - return 0; -} - -/// Ditto -bool endsWith(alias pred = "a == b", R1, R2)(R1 doesThisEnd, R2 withThis) -if (isBidirectionalRange!R1 && - isBidirectionalRange!R2 && - is(typeof(binaryFun!pred(doesThisEnd.back, withThis.back)) : bool)) -{ - alias haystack = doesThisEnd; - alias needle = withThis; - - static if (is(typeof(pred) : string)) - enum isDefaultPred = pred == "a == b"; - else - enum isDefaultPred = false; - - static if (isDefaultPred && isArray!R1 && isArray!R2 && - is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) - { - if (haystack.length < needle.length) return false; - - return haystack[$ - needle.length .. $] == needle; - } - else - { - return startsWith!pred(retro(doesThisEnd), retro(withThis)); - } -} - -/// Ditto -bool endsWith(alias pred = "a == b", R, E)(R doesThisEnd, E withThis) -if (isBidirectionalRange!R && - is(typeof(binaryFun!pred(doesThisEnd.back, withThis)) : bool)) -{ - return doesThisEnd.empty - ? false - : binaryFun!pred(doesThisEnd.back, withThis); -} - -/// -unittest -{ - assert(endsWith("abc", "")); - assert(!endsWith("abc", "b")); - assert(endsWith("abc", "a", 'c') == 2); - assert(endsWith("abc", "c", "a") == 1); - assert(endsWith("abc", "c", "c") == 1); - assert(endsWith("abc", "bc", "c") == 2); - assert(endsWith("abc", "x", "c", "b") == 2); - assert(endsWith("abc", "x", "aa", "bc") == 3); - assert(endsWith("abc", "x", "aaa", "sab") == 0); - assert(endsWith("abc", "x", "aaa", 'c', "sab") == 3); -} - -unittest -{ - import std.conv : to; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) - { - assert(!endsWith(to!S("abc"), 'a')); - assert(endsWith(to!S("abc"), 'a', 'c') == 2); - assert(!endsWith(to!S("abc"), 'x', 'n', 'b')); - assert(endsWith(to!S("abc"), 'x', 'n', 'c') == 3); - assert(endsWith(to!S("abc\uFF28"), 'a', '\uFF28', 'c') == 2); - - foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) - { - //Lots of strings - assert(endsWith(to!S("abc"), to!T(""))); - assert(!endsWith(to!S("abc"), to!T("a"))); - assert(!endsWith(to!S("abc"), to!T("b"))); - assert(endsWith(to!S("abc"), to!T("bc"), 'c') == 2); - assert(endsWith(to!S("abc"), to!T("a"), "c") == 2); - assert(endsWith(to!S("abc"), to!T("c"), "a") == 1); - assert(endsWith(to!S("abc"), to!T("c"), "c") == 1); - assert(endsWith(to!S("abc"), to!T("x"), 'c', "b") == 2); - assert(endsWith(to!S("abc"), 'x', to!T("aa"), "bc") == 3); - assert(endsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0); - assert(endsWith(to!S("abc"), to!T("x"), "aaa", "c", "sab") == 3); - assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co"))); - assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2); - - //Unicode - assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co"))); - assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2); - assert(endsWith(to!S("日本語"), to!T("本語"))); - assert(endsWith(to!S("日本語"), to!T("日本語"))); - assert(!endsWith(to!S("本語"), to!T("日本語"))); - - //Empty - assert(endsWith(to!S(""), T.init)); - assert(!endsWith(to!S(""), 'a')); - assert(endsWith(to!S("a"), T.init)); - assert(endsWith(to!S("a"), T.init, "") == 1); - assert(endsWith(to!S("a"), T.init, 'a') == 1); - assert(endsWith(to!S("a"), 'a', T.init) == 2); - } - } - - foreach (T; TypeTuple!(int, short)) - { - immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; - - //RA range - assert(endsWith(arr, cast(int[])null)); - assert(!endsWith(arr, 0)); - assert(!endsWith(arr, 4)); - assert(endsWith(arr, 5)); - assert(endsWith(arr, 0, 4, 5) == 3); - assert(endsWith(arr, [5])); - assert(endsWith(arr, [4, 5])); - assert(endsWith(arr, [4, 5], 7) == 1); - assert(!endsWith(arr, [2, 4, 5])); - assert(endsWith(arr, [2, 4, 5], [3, 4, 5]) == 2); - - //Normal input range - assert(!endsWith(filterBidirectional!"true"(arr), 4)); - assert(endsWith(filterBidirectional!"true"(arr), 5)); - assert(endsWith(filterBidirectional!"true"(arr), [5])); - assert(endsWith(filterBidirectional!"true"(arr), [4, 5])); - assert(endsWith(filterBidirectional!"true"(arr), [4, 5], 7) == 1); - assert(!endsWith(filterBidirectional!"true"(arr), [2, 4, 5])); - assert(endsWith(filterBidirectional!"true"(arr), [2, 4, 5], [3, 4, 5]) == 2); - assert(endsWith(arr, filterBidirectional!"true"([4, 5]))); - assert(endsWith(arr, filterBidirectional!"true"([4, 5]), 7) == 1); - assert(!endsWith(arr, filterBidirectional!"true"([2, 4, 5]))); - assert(endsWith(arr, [2, 4, 5], filterBidirectional!"true"([3, 4, 5])) == 2); - - //Non-default pred - assert(endsWith!("a%10 == b%10")(arr, [14, 15])); - assert(!endsWith!("a%10 == b%10")(arr, [15, 14])); - } -} - -/** -Returns the common prefix of two ranges. - -If the first argument is a string, then the result is a slice of $(D r1) which -contains the characters that both ranges start with. For all other types, the -type of the result is the same as the result of $(D takeExactly(r1, n)), where -$(D n) is the number of elements that both ranges start with. - -See_Also: - $(XREF range, takeExactly) - */ -auto commonPrefix(alias pred = "a == b", R1, R2)(R1 r1, R2 r2) -if (isForwardRange!R1 && isInputRange!R2 && - !isNarrowString!R1 && - is(typeof(binaryFun!pred(r1.front, r2.front)))) -{ - static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && - hasLength!R1 && hasLength!R2 && - hasSlicing!R1) - { - immutable limit = min(r1.length, r2.length); - foreach (i; 0 .. limit) - { - if (!binaryFun!pred(r1[i], r2[i])) - { - return r1[0 .. i]; - } - } - return r1[0 .. limit]; - } - else - { - auto result = r1.save; - size_t i = 0; - for (; - !r1.empty && !r2.empty && binaryFun!pred(r1.front, r2.front); - ++i, r1.popFront(), r2.popFront()) - {} - return takeExactly(result, i); - } -} - -/// -unittest -{ - assert(commonPrefix("hello, world", "hello, there") == "hello, "); -} - -auto commonPrefix(alias pred, R1, R2)(R1 r1, R2 r2) -if (isNarrowString!R1 && isInputRange!R2 && - is(typeof(binaryFun!pred(r1.front, r2.front)))) -{ - import std.utf : decode; - - auto result = r1.save; - immutable len = r1.length; - size_t i = 0; - - for (size_t j = 0; i < len && !r2.empty; r2.popFront(), i = j) - { - immutable f = decode(r1, j); - if (!binaryFun!pred(f, r2.front)) - break; - } - - return result[0 .. i]; -} - -auto commonPrefix(R1, R2)(R1 r1, R2 r2) -if (isNarrowString!R1 && isInputRange!R2 && !isNarrowString!R2 && - is(typeof(r1.front == r2.front))) -{ - return commonPrefix!"a == b"(r1, r2); -} - -auto commonPrefix(R1, R2)(R1 r1, R2 r2) -if (isNarrowString!R1 && isNarrowString!R2) -{ - static if (ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof) - { - import std.utf : stride, UTFException; - - immutable limit = min(r1.length, r2.length); - for (size_t i = 0; i < limit;) - { - immutable codeLen = std.utf.stride(r1, i); - size_t j = 0; - - for (; j < codeLen && i < limit; ++i, ++j) - { - if (r1[i] != r2[i]) - return r1[0 .. i - j]; - } - - if (i == limit && j < codeLen) - throw new UTFException("Invalid UTF-8 sequence", i); - } - return r1[0 .. limit]; - } - else - return commonPrefix!"a == b"(r1, r2); -} - -unittest -{ - import std.conv : to; - import std.exception : assertThrown; - import std.utf : UTFException; - - assert(commonPrefix([1, 2, 3], [1, 2, 3, 4, 5]) == [1, 2, 3]); - assert(commonPrefix([1, 2, 3, 4, 5], [1, 2, 3]) == [1, 2, 3]); - assert(commonPrefix([1, 2, 3, 4], [1, 2, 3, 4]) == [1, 2, 3, 4]); - assert(commonPrefix([1, 2, 3], [7, 2, 3, 4, 5]).empty); - assert(commonPrefix([7, 2, 3, 4, 5], [1, 2, 3]).empty); - assert(commonPrefix([1, 2, 3], cast(int[])null).empty); - assert(commonPrefix(cast(int[])null, [1, 2, 3]).empty); - assert(commonPrefix(cast(int[])null, cast(int[])null).empty); - - foreach (S; TypeTuple!(char[], const(char)[], string, - wchar[], const(wchar)[], wstring, - dchar[], const(dchar)[], dstring)) - { - foreach(T; TypeTuple!(string, wstring, dstring)) - { - assert(commonPrefix(to!S(""), to!T("")).empty); - assert(commonPrefix(to!S(""), to!T("hello")).empty); - assert(commonPrefix(to!S("hello"), to!T("")).empty); - assert(commonPrefix(to!S("hello, world"), to!T("hello, there")) == to!S("hello, ")); - assert(commonPrefix(to!S("hello, there"), to!T("hello, world")) == to!S("hello, ")); - assert(commonPrefix(to!S("hello, "), to!T("hello, world")) == to!S("hello, ")); - assert(commonPrefix(to!S("hello, world"), to!T("hello, ")) == to!S("hello, ")); - assert(commonPrefix(to!S("hello, world"), to!T("hello, world")) == to!S("hello, world")); - - //Bug# 8890 - assert(commonPrefix(to!S("Пиво"), to!T("Пони"))== to!S("П")); - assert(commonPrefix(to!S("Пони"), to!T("Пиво"))== to!S("П")); - assert(commonPrefix(to!S("Пиво"), to!T("Пиво"))== to!S("Пиво")); - assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFE"), - to!T("\U0010FFFF\U0010FFFB\U0010FFFC")) == to!S("\U0010FFFF\U0010FFFB")); - assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFC"), - to!T("\U0010FFFF\U0010FFFB\U0010FFFE")) == to!S("\U0010FFFF\U0010FFFB")); - assert(commonPrefix!"a != b"(to!S("Пиво"), to!T("онво")) == to!S("Пи")); - assert(commonPrefix!"a != b"(to!S("онво"), to!T("Пиво")) == to!S("он")); - } - - static assert(is(typeof(commonPrefix(to!S("Пиво"), filter!"true"("Пони"))) == S)); - assert(equal(commonPrefix(to!S("Пиво"), filter!"true"("Пони")), to!S("П"))); - - static assert(is(typeof(commonPrefix(filter!"true"("Пиво"), to!S("Пони"))) == - typeof(takeExactly(filter!"true"("П"), 1)))); - assert(equal(commonPrefix(filter!"true"("Пиво"), to!S("Пони")), takeExactly(filter!"true"("П"), 1))); - } - - assertThrown!UTFException(commonPrefix("\U0010FFFF\U0010FFFB", "\U0010FFFF\U0010FFFB"[0 .. $ - 1])); - - assert(commonPrefix("12345"d, [49, 50, 51, 60, 60]) == "123"d); - assert(commonPrefix([49, 50, 51, 60, 60], "12345" ) == [49, 50, 51]); - assert(commonPrefix([49, 50, 51, 60, 60], "12345"d) == [49, 50, 51]); - - assert(commonPrefix!"a == ('0' + b)"("12345" , [1, 2, 3, 9, 9]) == "123"); - assert(commonPrefix!"a == ('0' + b)"("12345"d, [1, 2, 3, 9, 9]) == "123"d); - assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345" ) == [1, 2, 3]); - assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345"d) == [1, 2, 3]); -} - -// findAdjacent -/** -Advances $(D r) until it finds the first two adjacent elements $(D a), -$(D b) that satisfy $(D pred(a, b)). Performs $(BIGOH r.length) -evaluations of $(D pred). - -See_Also: - $(WEB sgi.com/tech/stl/adjacent_find.html, STL's adjacent_find) -*/ -Range findAdjacent(alias pred = "a == b", Range)(Range r) - if (isForwardRange!(Range)) -{ - auto ahead = r.save; - if (!ahead.empty) - { - for (ahead.popFront(); !ahead.empty; r.popFront(), ahead.popFront()) - { - if (binaryFun!(pred)(r.front, ahead.front)) return r; - } - } - static if (!isInfinite!Range) - return ahead; -} - -/// -unittest -{ - int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; - auto r = findAdjacent(a); - assert(r == [ 10, 10, 9, 8, 8, 7, 8, 9 ]); - auto p = findAdjacent!("a < b")(a); - assert(p == [ 7, 8, 9 ]); - -} - -unittest -{ - //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; - auto p = findAdjacent(a); - assert(p == [10, 10, 9, 8, 8, 7, 8, 9 ]); - p = findAdjacent!("a < b")(a); - assert(p == [7, 8, 9]); - // empty - a = []; - p = findAdjacent(a); - assert(p.empty); - // not found - a = [ 1, 2, 3, 4, 5 ]; - p = findAdjacent(a); - assert(p.empty); - p = findAdjacent!"a > b"(a); - assert(p.empty); - ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]); - assert(equal(findAdjacent(rfr), [2, 2, 3])); - - // Issue 9350 - assert(!repeat(1).findAdjacent().empty); -} - -// findAmong -/** -Advances $(D seq) by calling $(D seq.popFront) until either $(D -find!(pred)(choices, seq.front)) is $(D true), or $(D seq) becomes -empty. Performs $(BIGOH seq.length * choices.length) evaluations of -$(D pred). - -See_Also: - $(WEB sgi.com/tech/stl/find_first_of.html, STL's find_first_of) -*/ -Range1 findAmong(alias pred = "a == b", Range1, Range2)( - Range1 seq, Range2 choices) - if (isInputRange!Range1 && isForwardRange!Range2) -{ - for (; !seq.empty && find!pred(choices, seq.front).empty; seq.popFront()) - { - } - return seq; -} - -/// -unittest -{ - int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; - int[] b = [ 3, 1, 2 ]; - assert(findAmong(a, b) == a[2 .. $]); -} - -unittest -{ - //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ -1, 0, 2, 1, 2, 3, 4, 5 ]; - int[] b = [ 1, 2, 3 ]; - assert(findAmong(a, b) == [2, 1, 2, 3, 4, 5 ]); - assert(findAmong(b, [ 4, 6, 7 ][]).empty); - assert(findAmong!("a==b")(a, b).length == a.length - 2); - assert(findAmong!("a==b")(b, [ 4, 6, 7 ][]).empty); -} - -// count -/** -The first version counts the number of elements $(D x) in $(D r) for -which $(D pred(x, value)) is $(D true). $(D pred) defaults to -equality. Performs $(BIGOH haystack.length) evaluations of $(D pred). - -The second version returns the number of times $(D needle) occurs in -$(D haystack). Throws an exception if $(D needle.empty), as the _count -of the empty range in any range would be infinite. Overlapped counts -are not considered, for example $(D count("aaa", "aa")) is $(D 1), not -$(D 2). - -The third version counts the elements for which $(D pred(x)) is $(D -true). Performs $(BIGOH haystack.length) evaluations of $(D pred). - -Note: Regardless of the overload, $(D count) will not accept -infinite ranges for $(D haystack). -*/ -size_t count(alias pred = "a == b", Range, E)(Range haystack, E needle) - if (isInputRange!Range && !isInfinite!Range && - is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) -{ - bool pred2(ElementType!Range a) { return binaryFun!pred(a, needle); } - return count!pred2(haystack); -} - -/// -unittest -{ - import std.uni : toLower; - - // count elements in range - int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; - assert(count(a, 2) == 3); - assert(count!("a > b")(a, 2) == 5); - // count range in range - assert(count("abcadfabf", "ab") == 2); - assert(count("ababab", "abab") == 1); - assert(count("ababab", "abx") == 0); - // fuzzy count range in range - assert(count!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))("AbcAdFaBf", "ab") == 2); - // count predicate in range - assert(count!("a > 1")(a) == 8); -} - -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; - assert(count(a, 2) == 3, text(count(a, 2))); - assert(count!("a > b")(a, 2) == 5, text(count!("a > b")(a, 2))); - - // check strings - assert(count("日本語") == 3); - assert(count("日本語"w) == 3); - assert(count("日本語"d) == 3); - - assert(count!("a == '日'")("日本語") == 1); - assert(count!("a == '本'")("日本語"w) == 1); - assert(count!("a == '語'")("日本語"d) == 1); -} - -unittest -{ - debug(std_algorithm) printf("algorithm.count.unittest\n"); - string s = "This is a fofofof list"; - string sub = "fof"; - assert(count(s, sub) == 2); -} - -/// Ditto -size_t count(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) - if (isForwardRange!R1 && !isInfinite!R1 && - isForwardRange!R2 && - is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) -{ - assert(!needle.empty, "Cannot count occurrences of an empty range"); - - static if (isInfinite!R2) - { - //Note: This is the special case of looking for an infinite inside a finite... - //"How many instances of the Fibonacci sequence can you count in [1, 2, 3]?" - "None." - return 0; - } - else - { - size_t result; - //Note: haystack is not saved, because findskip is designed to modify it - for ( ; findSkip!pred(haystack, needle.save) ; ++result) - {} - return result; - } -} - -/// Ditto -size_t count(alias pred = "true", R)(R haystack) - if (isInputRange!R && !isInfinite!R && - is(typeof(unaryFun!pred(haystack.front)) : bool)) -{ - size_t result; - alias T = ElementType!R; //For narrow strings forces dchar iteration - foreach (T elem; haystack) - if (unaryFun!pred(elem)) ++result; - return result; -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; - assert(count!("a == 3")(a) == 2); - assert(count("日本語") == 3); -} - -// Issue 11253 -nothrow unittest -{ - assert([1, 2, 3].count([2, 3]) == 1); -} - -// balancedParens -/** -Checks whether $(D r) has "balanced parentheses", i.e. all instances -of $(D lPar) are closed by corresponding instances of $(D rPar). The -parameter $(D maxNestingLevel) controls the nesting level allowed. The -most common uses are the default or $(D 0). In the latter case, no -nesting is allowed. -*/ -bool balancedParens(Range, E)(Range r, E lPar, E rPar, - size_t maxNestingLevel = size_t.max) -if (isInputRange!(Range) && is(typeof(r.front == lPar))) -{ - size_t count; - for (; !r.empty; r.popFront()) - { - if (r.front == lPar) - { - if (count > maxNestingLevel) return false; - ++count; - } - else if (r.front == rPar) - { - if (!count) return false; - --count; - } - } - return count == 0; -} - -/// -unittest -{ - auto s = "1 + (2 * (3 + 1 / 2)"; - assert(!balancedParens(s, '(', ')')); - s = "1 + (2 * (3 + 1) / 2)"; - assert(balancedParens(s, '(', ')')); - s = "1 + (2 * (3 + 1) / 2)"; - assert(!balancedParens(s, '(', ')', 0)); - s = "1 + (2 * 3 + 1) / (2 - 5)"; - assert(balancedParens(s, '(', ')', 0)); -} - -// equal -/** -Compares two ranges for equality, as defined by predicate $(D pred) -(which is $(D ==) by default). -*/ -template equal(alias pred = "a == b") -{ - /++ - Returns $(D true) if and only if the two ranges compare equal element - for element, according to binary predicate $(D pred). The ranges may - have different element types, as long as $(D pred(a, b)) evaluates to - $(D bool) for $(D a) in $(D r1) and $(D b) in $(D r2). Performs - $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred). - - See_Also: - $(WEB sgi.com/tech/stl/_equal.html, STL's _equal) - +/ - bool equal(Range1, Range2)(Range1 r1, Range2 r2) - if (isInputRange!Range1 && isInputRange!Range2 && is(typeof(binaryFun!pred(r1.front, r2.front)))) - { - //Start by detecting default pred and compatible dynamicarray. - static if (is(typeof(pred) == string) && pred == "a == b" && - isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2))) - { - return r1 == r2; - } - //Try a fast implementation when the ranges have comparable lengths - else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length))) - { - auto len1 = r1.length; - auto len2 = r2.length; - if (len1 != len2) return false; //Short circuit return - - //Lengths are the same, so we need to do an actual comparison - //Good news is we can sqeeze out a bit of performance by not checking if r2 is empty - for (; !r1.empty; r1.popFront(), r2.popFront()) - { - if (!binaryFun!(pred)(r1.front, r2.front)) return false; - } - return true; - } - else - { - //Generic case, we have to walk both ranges making sure neither is empty - for (; !r1.empty; r1.popFront(), r2.popFront()) - { - if (r2.empty) return false; - if (!binaryFun!(pred)(r1.front, r2.front)) return false; - } - return r2.empty; - } - } -} - -/// -unittest -{ - import std.math : approxEqual; - import std.algorithm : equal; - - int[] a = [ 1, 2, 4, 3 ]; - assert(!equal(a, a[1..$])); - assert(equal(a, a)); - - // different types - double[] b = [ 1.0, 2, 4, 3]; - assert(!equal(a, b[1..$])); - assert(equal(a, b)); - - // predicated: ensure that two vectors are approximately equal - double[] c = [ 1.005, 2, 4, 3]; - assert(equal!approxEqual(b, c)); -} - -/++ -Tip: $(D equal) can itself be used as a predicate to other functions. -This can be very useful when the element type of a range is itself a -range. In particular, $(D equal) can be its own predicate, allowing -range of range (of range...) comparisons. - +/ -unittest -{ - import std.algorithm : equal; - import std.range : iota, chunks; - assert(equal!(equal!equal)( - [[[0, 1], [2, 3]], [[4, 5], [6, 7]]], - iota(0, 8).chunks(2).chunks(2) - )); -} - -unittest -{ - import std.math : approxEqual; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - // various strings - assert(equal("æøå", "æøå")); //UTF8 vs UTF8 - assert(!equal("???", "æøå")); //UTF8 vs UTF8 - assert(equal("æøå"w, "æøå"d)); //UTF16 vs UTF32 - assert(!equal("???"w, "æøå"d));//UTF16 vs UTF32 - assert(equal("æøå"d, "æøå"d)); //UTF32 vs UTF32 - assert(!equal("???"d, "æøå"d));//UTF32 vs UTF32 - assert(!equal("hello", "world")); - - // same strings, but "explicit non default" comparison (to test the non optimized array comparison) - assert( equal!("a==b")("æøå", "æøå")); //UTF8 vs UTF8 - assert(!equal!("a==b")("???", "æøå")); //UTF8 vs UTF8 - assert( equal!("a==b")("æøå"w, "æøå"d)); //UTF16 vs UTF32 - assert(!equal!("a==b")("???"w, "æøå"d));//UTF16 vs UTF32 - assert( equal!("a==b")("æøå"d, "æøå"d)); //UTF32 vs UTF32 - assert(!equal!("a==b")("???"d, "æøå"d));//UTF32 vs UTF32 - assert(!equal!("a==b")("hello", "world")); - - //Array of string - assert(equal(["hello", "world"], ["hello", "world"])); - assert(!equal(["hello", "world"], ["hello"])); - assert(!equal(["hello", "world"], ["hello", "Bob!"])); - - //Should not compile, because "string == dstring" is illegal - static assert(!is(typeof(equal(["hello", "world"], ["hello"d, "world"d])))); - //However, arrays of non-matching string can be compared using equal!equal. Neat-o! - equal!equal(["hello", "world"], ["hello"d, "world"d]); - - //Tests, with more fancy map ranges - int[] a = [ 1, 2, 4, 3 ]; - assert(equal([2, 4, 8, 6], map!"a*2"(a))); - double[] b = [ 1.0, 2, 4, 3]; - double[] c = [ 1.005, 2, 4, 3]; - assert(equal!approxEqual(map!"a*2"(b), map!"a*2"(c))); - assert(!equal([2, 4, 1, 3], map!"a*2"(a))); - assert(!equal([2, 4, 1], map!"a*2"(a))); - assert(!equal!approxEqual(map!"a*3"(b), map!"a*2"(c))); - - //Tests with some fancy reference ranges. - ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]); - ReferenceForwardRange!int cfr = new ReferenceForwardRange!int([1, 2, 4, 3]); - assert(equal(cir, a)); - cir = new ReferenceInputRange!int([1, 2, 4, 3]); - assert(equal(cir, cfr.save)); - assert(equal(cfr.save, cfr.save)); - cir = new ReferenceInputRange!int([1, 2, 8, 1]); - assert(!equal(cir, cfr)); - - //Test with an infinte range - ReferenceInfiniteForwardRange!int ifr = new ReferenceInfiniteForwardRange!int; - assert(!equal(a, ifr)); -} - -// cmp -/********************************** -Performs three-way lexicographical comparison on two input ranges -according to predicate $(D pred). Iterating $(D r1) and $(D r2) in -lockstep, $(D cmp) compares each element $(D e1) of $(D r1) with the -corresponding element $(D e2) in $(D r2). If $(D binaryFun!pred(e1, -e2)), $(D cmp) returns a negative value. If $(D binaryFun!pred(e2, -e1)), $(D cmp) returns a positive value. If one of the ranges has been -finished, $(D cmp) returns a negative value if $(D r1) has fewer -elements than $(D r2), a positive value if $(D r1) has more elements -than $(D r2), and $(D 0) if the ranges have the same number of -elements. - -If the ranges are strings, $(D cmp) performs UTF decoding -appropriately and compares the ranges one code point at a time. -*/ -int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) -if (isInputRange!R1 && isInputRange!R2 && !(isSomeString!R1 && isSomeString!R2)) -{ - for (;; r1.popFront(), r2.popFront()) - { - if (r1.empty) return -cast(int)!r2.empty; - if (r2.empty) return !r1.empty; - auto a = r1.front, b = r2.front; - if (binaryFun!pred(a, b)) return -1; - if (binaryFun!pred(b, a)) return 1; - } -} - -// Specialization for strings (for speed purposes) -int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) if (isSomeString!R1 && isSomeString!R2) -{ - import core.stdc.string : memcmp; - import std.utf : decode; - - static if (is(typeof(pred) : string)) - enum isLessThan = pred == "a < b"; - else - enum isLessThan = false; - - // For speed only - static int threeWay(size_t a, size_t b) - { - static if (size_t.sizeof == int.sizeof && isLessThan) - return a - b; - else - return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0; - } - // For speed only - // @@@BUG@@@ overloading should be allowed for nested functions - static int threeWayInt(int a, int b) - { - static if (isLessThan) - return a - b; - else - return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0; - } - - static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof && isLessThan) - { - static if (typeof(r1[0]).sizeof == 1) - { - immutable len = min(r1.length, r2.length); - immutable result = __ctfe ? - { - foreach (i; 0 .. len) - { - if (r1[i] != r2[i]) - return threeWayInt(r1[i], r2[i]); - } - return 0; - }() - : core.stdc.string.memcmp(r1.ptr, r2.ptr, len); - if (result) return result; - } - else - { - auto p1 = r1.ptr, p2 = r2.ptr, - pEnd = p1 + min(r1.length, r2.length); - for (; p1 != pEnd; ++p1, ++p2) - { - if (*p1 != *p2) return threeWayInt(cast(int) *p1, cast(int) *p2); - } - } - return threeWay(r1.length, r2.length); - } - else - { - for (size_t i1, i2;;) - { - if (i1 == r1.length) return threeWay(i2, r2.length); - if (i2 == r2.length) return threeWay(r1.length, i1); - immutable c1 = std.utf.decode(r1, i1), - c2 = std.utf.decode(r2, i2); - if (c1 != c2) return threeWayInt(cast(int) c1, cast(int) c2); - } - } -} - -/// -unittest -{ - int result; - - debug(string) printf("string.cmp.unittest\n"); - result = cmp("abc", "abc"); - assert(result == 0); - // result = cmp(null, null); - // assert(result == 0); - result = cmp("", ""); - assert(result == 0); - result = cmp("abc", "abcd"); - assert(result < 0); - result = cmp("abcd", "abc"); - assert(result > 0); - result = cmp("abc"d, "abd"); - assert(result < 0); - result = cmp("bbc", "abc"w); - assert(result > 0); - result = cmp("aaa", "aaaa"d); - assert(result < 0); - result = cmp("aaaa", "aaa"d); - assert(result > 0); - result = cmp("aaa", "aaa"d); - assert(result == 0); - result = cmp(cast(int[])[], cast(int[])[]); - assert(result == 0); - result = cmp([1, 2, 3], [1, 2, 3]); - assert(result == 0); - result = cmp([1, 3, 2], [1, 2, 3]); - assert(result > 0); - result = cmp([1, 2, 3], [1L, 2, 3, 4]); - assert(result < 0); - result = cmp([1L, 2, 3], [1, 2]); - assert(result > 0); -} - -// MinType -private template MinType(T...) - if (T.length >= 1) -{ - static if (T.length == 1) - { - alias MinType = T[0]; - } - else static if (T.length == 2) - { - static if (!is(typeof(T[0].min))) - alias MinType = CommonType!T; - else - { - enum hasMostNegative = is(typeof(mostNegative!(T[0]))) && - is(typeof(mostNegative!(T[1]))); - static if (hasMostNegative && mostNegative!(T[1]) < mostNegative!(T[0])) - alias MinType = T[1]; - else static if (hasMostNegative && mostNegative!(T[1]) > mostNegative!(T[0])) - alias MinType = T[0]; - else static if (T[1].max < T[0].max) - alias MinType = T[1]; - else - alias MinType = T[0]; - } - } - else - { - alias MinType = MinType!(MinType!(T[0 .. ($+1)/2]), MinType!(T[($+1)/2 .. $])); - } -} - -// min -/** -Returns the minimum of the passed-in values. -*/ -MinType!T min(T...)(T args) - if (T.length >= 2) -{ - //Get "a" - static if (T.length <= 2) - alias args[0] a; - else - auto a = min(args[0 .. ($+1)/2]); - alias typeof(a) T0; - - //Get "b" - static if (T.length <= 3) - alias args[$-1] b; - else - auto b = min(args[($+1)/2 .. $]); - alias typeof(b) T1; - - static assert (is(typeof(a < b)), - algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); - - //Do the "min" proper with a and b - import std.functional : lessThan; - immutable chooseA = lessThan!(T0, T1)(a, b); - return cast(typeof(return)) (chooseA ? a : b); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int a = 5; - short b = 6; - double c = 2; - auto d = min(a, b); - static assert(is(typeof(d) == int)); - assert(d == 5); - auto e = min(a, b, c); - static assert(is(typeof(e) == double)); - assert(e == 2); - // mixed signedness test - a = -10; - uint f = 10; - static assert(is(typeof(min(a, f)) == int)); - assert(min(a, f) == -10); - - //Test user-defined types - import std.datetime; - assert(min(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(1982, 1, 4)); - assert(min(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(1982, 1, 4)); - assert(min(Date(1982, 1, 4), Date.min) == Date.min); - assert(min(Date.min, Date(1982, 1, 4)) == Date.min); - assert(min(Date(1982, 1, 4), Date.max) == Date(1982, 1, 4)); - assert(min(Date.max, Date(1982, 1, 4)) == Date(1982, 1, 4)); - assert(min(Date.min, Date.max) == Date.min); - assert(min(Date.max, Date.min) == Date.min); -} - -// MaxType -private template MaxType(T...) - if (T.length >= 1) -{ - static if (T.length == 1) - { - alias MaxType = T[0]; - } - else static if (T.length == 2) - { - static if (!is(typeof(T[0].min))) - alias MaxType = CommonType!T; - else static if (T[1].max > T[0].max) - alias MaxType = T[1]; - else - alias MaxType = T[0]; - } - else - { - alias MaxType = MaxType!(MaxType!(T[0 .. ($+1)/2]), MaxType!(T[($+1)/2 .. $])); - } -} - -// max -/** -Returns the maximum of the passed-in values. -*/ -MaxType!T max(T...)(T args) - if (T.length >= 2) -{ - //Get "a" - static if (T.length <= 2) - alias args[0] a; - else - auto a = max(args[0 .. ($+1)/2]); - alias typeof(a) T0; - - //Get "b" - static if (T.length <= 3) - alias args[$-1] b; - else - auto b = max(args[($+1)/2 .. $]); - alias typeof(b) T1; - - static assert (is(typeof(a < b)), - algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); - - //Do the "max" proper with a and b - import std.functional : lessThan; - immutable chooseB = lessThan!(T0, T1)(a, b); - return cast(typeof(return)) (chooseB ? b : a); -} - -/// -unittest -{ - int a = 5; - short b = 6; - double c = 2; - auto d = max(a, b); - assert(is(typeof(d) == int)); - assert(d == 6); - auto e = min(a, b, c); - assert(is(typeof(e) == double)); - assert(e == 2); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int a = 5; - short b = 6; - double c = 2; - auto d = max(a, b); - static assert(is(typeof(d) == int)); - assert(d == 6); - auto e = max(a, b, c); - static assert(is(typeof(e) == double)); - assert(e == 6); - // mixed sign - a = -5; - uint f = 5; - static assert(is(typeof(max(a, f)) == uint)); - assert(max(a, f) == 5); - - //Test user-defined types - import std.datetime; - assert(max(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(2012, 12, 21)); - assert(max(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(2012, 12, 21)); - assert(max(Date(1982, 1, 4), Date.min) == Date(1982, 1, 4)); - assert(max(Date.min, Date(1982, 1, 4)) == Date(1982, 1, 4)); - assert(max(Date(1982, 1, 4), Date.max) == Date.max); - assert(max(Date.max, Date(1982, 1, 4)) == Date.max); - assert(max(Date.min, Date.max) == Date.max); - assert(max(Date.max, Date.min) == Date.max); -} - -/** -Returns $(D val), if it is between $(D lower) and $(D upper). -Otherwise returns the nearest of the two. Equivalent to $(D max(lower, -min(upper,val))). -*/ -auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) -in -{ - import std.functional : greaterThan; - assert(!lower.greaterThan(upper)); -} -body -{ - return max(lower, min(upper, val)); -} - -/// -unittest -{ - assert(clamp(2, 1, 3) == 2); - assert(clamp(0, 1, 3) == 1); - assert(clamp(4, 1, 3) == 3); - - assert(clamp(1, 1, 1) == 1); - - assert(clamp(5, -1, 2u) == 2); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int a = 1; - short b = 6; - double c = 2; - static assert(is(typeof(clamp(c,a,b)) == double)); - assert(clamp(c, a, b) == c); - assert(clamp(a-c, a, b) == a); - assert(clamp(b+c, a, b) == b); - // mixed sign - a = -5; - uint f = 5; - static assert(is(typeof(clamp(f, a, b)) == int)); - assert(clamp(f, a, b) == f); - // similar type deduction for (u)long - static assert(is(typeof(clamp(-1L, -2L, 2UL)) == long)); - - // user-defined types - import std.datetime; - assert(clamp(Date(1982, 1, 4), Date(1012, 12, 21), Date(2012, 12, 21)) == Date(1982, 1, 4)); - assert(clamp(Date(1982, 1, 4), Date.min, Date.max) == Date(1982, 1, 4)); - // UFCS style - assert(Date(1982, 1, 4).clamp(Date.min, Date.max) == Date(1982, 1, 4)); - -} - -/** -Returns the minimum element of a range together with the number of -occurrences. The function can actually be used for counting the -maximum or any other ordering predicate (that's why $(D maxCount) is -not provided). - */ -Tuple!(ElementType!Range, size_t) -minCount(alias pred = "a < b", Range)(Range range) - if (isInputRange!Range && !isInfinite!Range && - is(typeof(binaryFun!pred(range.front, range.front)))) -{ - import std.exception : enforce; - - alias T = ElementType!Range; - alias UT = Unqual!T; - alias RetType = Tuple!(T, size_t); - - static assert (is(typeof(RetType(range.front, 1))), - algoFormat("Error: Cannot call minCount on a %s, because it is not possible "~ - "to copy the result value (a %s) into a Tuple.", Range.stringof, T.stringof)); - - enforce(!range.empty, "Can't count elements from an empty range"); - size_t occurrences = 1; - - static if (isForwardRange!Range) - { - Range least = range.save; - for (range.popFront(); !range.empty; range.popFront()) - { - if (binaryFun!pred(least.front, range.front)) continue; - if (binaryFun!pred(range.front, least.front)) - { - // change the min - least = range.save; - occurrences = 1; - } - else - ++occurrences; - } - return RetType(least.front, occurrences); - } - else static if (isAssignable!(UT, T) || (!hasElaborateAssign!UT && isAssignable!UT)) - { - UT v = UT.init; - static if (isAssignable!(UT, T)) v = range.front; - else v = cast(UT)range.front; - - for (range.popFront(); !range.empty; range.popFront()) - { - if (binaryFun!pred(*cast(T*)&v, range.front)) continue; - if (binaryFun!pred(range.front, *cast(T*)&v)) - { - // change the min - static if (isAssignable!(UT, T)) v = range.front; - else v = cast(UT)range.front; //Safe because !hasElaborateAssign!UT - occurrences = 1; - } - else - ++occurrences; - } - return RetType(*cast(T*)&v, occurrences); - } - else static if (hasLvalueElements!Range) - { - T* p = addressOf(range.front); - for (range.popFront(); !range.empty; range.popFront()) - { - if (binaryFun!pred(*p, range.front)) continue; - if (binaryFun!pred(range.front, *p)) - { - // change the min - p = addressOf(range.front); - occurrences = 1; - } - else - ++occurrences; - } - return RetType(*p, occurrences); - } - else - static assert(false, - algoFormat("Sorry, can't find the minCount of a %s: Don't know how "~ - "to keep track of the smallest %s element.", Range.stringof, T.stringof)); -} - -/// -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; - // Minimum is 1 and occurs 3 times - assert(minCount(a) == tuple(1, 3)); - // Maximum is 4 and occurs 2 times - assert(minCount!("a > b")(a) == tuple(4, 2)); -} - -unittest -{ - import std.conv : text; - import std.exception : assertThrown; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[][] b = [ [4], [2, 4], [4], [4] ]; - auto c = minCount!("a[0] < b[0]")(b); - assert(c == tuple([2, 4], 1), text(c[0])); - - //Test empty range - assertThrown(minCount(b[$..$])); - - //test with reference ranges. Test both input and forward. - assert(minCount(new ReferenceInputRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2)); - assert(minCount(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2)); -} - -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - static struct R(T) //input range - { - T[] arr; - alias arr this; - } - - immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; - R!(immutable int) b = R!(immutable int)(a); - - assert(minCount(a) == tuple(1, 3)); - assert(minCount(b) == tuple(1, 3)); - assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(a) == tuple(4, 2)); - assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(b) == tuple(4, 2)); - - immutable(int[])[] c = [ [4], [2, 4], [4], [4] ]; - assert(minCount!("a[0] < b[0]")(c) == tuple([2, 4], 1), text(c[0])); - - static struct S1 - { - int i; - } - alias IS1 = immutable(S1); - static assert( isAssignable!S1); - static assert( isAssignable!(S1, IS1)); - - static struct S2 - { - int* p; - this(ref immutable int i) immutable {p = &i;} - this(ref int i) {p = &i;} - @property ref inout(int) i() inout {return *p;} - bool opEquals(const S2 other) const {return i == other.i;} - } - alias IS2 = immutable(S2); - static assert( isAssignable!S2); - static assert(!isAssignable!(S2, IS2)); - static assert(!hasElaborateAssign!S2); - - static struct S3 - { - int i; - void opAssign(ref S3 other) @disable; - } - static assert(!isAssignable!S3); - - foreach (Type; TypeTuple!(S1, IS1, S2, IS2, S3)) - { - static if (is(Type == immutable)) alias V = immutable int; - else alias V = int; - V one = 1, two = 2; - auto r1 = [Type(two), Type(one), Type(one)]; - auto r2 = R!Type(r1); - assert(minCount!"a.i < b.i"(r1) == tuple(Type(one), 2)); - assert(minCount!"a.i < b.i"(r2) == tuple(Type(one), 2)); - assert(one == 1 && two == 2); - } -} - -// minPos -/** -Returns the position of the minimum element of forward range $(D -range), i.e. a subrange of $(D range) starting at the position of its -smallest element and with the same ending as $(D range). The function -can actually be used for finding the maximum or any other ordering -predicate (that's why $(D maxPos) is not provided). - */ -Range minPos(alias pred = "a < b", Range)(Range range) - if (isForwardRange!Range && !isInfinite!Range && - is(typeof(binaryFun!pred(range.front, range.front)))) -{ - if (range.empty) return range; - auto result = range.save; - - for (range.popFront(); !range.empty; range.popFront()) - { - //Note: Unlike minCount, we do not care to find equivalence, so a single pred call is enough - if (binaryFun!pred(range.front, result.front)) - { - // change the min - result = range.save; - } - } - return result; -} - -/// -unittest -{ - int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; - // Minimum is 1 and first occurs in position 3 - assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]); - // Maximum is 4 and first occurs in position 2 - assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; - //Test that an empty range works - int[] b = a[$..$]; - assert(equal(minPos(b), b)); - - //test with reference range. - assert( equal( minPos(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])), [0, 2, 0] ) ); -} - -unittest -{ - //Rvalue range - import std.container : Array; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - assert(Array!int(2, 3, 4, 1, 2, 4, 1, 1, 2) - [] - .minPos() - .equal([ 1, 2, 4, 1, 1, 2 ])); -} - -unittest -{ - //BUG 9299 - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; - // Minimum is 1 and first occurs in position 3 - assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]); - // Maximum is 4 and first occurs in position 5 - assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]); - - immutable(int[])[] b = [ [4], [2, 4], [4], [4] ]; - assert(minPos!("a[0] < b[0]")(b) == [ [2, 4], [4], [4] ]); -} - -// mismatch -/** -Sequentially compares elements in $(D r1) and $(D r2) in lockstep, and -stops at the first mismatch (according to $(D pred), by default -equality). Returns a tuple with the reduced ranges that start with the -two mismatched values. Performs $(BIGOH min(r1.length, r2.length)) -evaluations of $(D pred). - -See_Also: - $(WEB sgi.com/tech/stl/_mismatch.html, STL's _mismatch) -*/ -Tuple!(Range1, Range2) -mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2) - if (isInputRange!(Range1) && isInputRange!(Range2)) -{ - for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) - { - if (!binaryFun!(pred)(r1.front, r2.front)) break; - } - return tuple(r1, r2); -} - -/// -unittest -{ - int[] x = [ 1, 5, 2, 7, 4, 3 ]; - double[] y = [ 1.0, 5, 2, 7.3, 4, 8 ]; - auto m = mismatch(x, y); - assert(m[0] == x[3 .. $]); - assert(m[1] == y[3 .. $]); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = [ 1, 2, 3 ]; - int[] b = [ 1, 2, 4, 5 ]; - auto mm = mismatch(a, b); - assert(mm[0] == [3]); - assert(mm[1] == [4, 5]); -} - -// levenshteinDistance -/** -Encodes $(WEB realityinteractive.com/rgrzywinski/archives/000249.html, -edit operations) necessary to transform one sequence into -another. Given sequences $(D s) (source) and $(D t) (target), a -sequence of $(D EditOp) encodes the steps that need to be taken to -convert $(D s) into $(D t). For example, if $(D s = "cat") and $(D -"cars"), the minimal sequence that transforms $(D s) into $(D t) is: -skip two characters, replace 't' with 'r', and insert an 's'. Working -with edit operations is useful in applications such as spell-checkers -(to find the closest word to a given misspelled word), approximate -searches, diff-style programs that compute the difference between -files, efficient encoding of patches, DNA sequence analysis, and -plagiarism detection. -*/ - -enum EditOp : char -{ - /** Current items are equal; no editing is necessary. */ - none = 'n', - /** Substitute current item in target with current item in source. */ - substitute = 's', - /** Insert current item from the source into the target. */ - insert = 'i', - /** Remove current item from the target. */ - remove = 'r' -} - -struct Levenshtein(Range, alias equals, CostType = size_t) -{ - void deletionIncrement(CostType n) - { - _deletionIncrement = n; - InitMatrix(); - } - - void insertionIncrement(CostType n) - { - _insertionIncrement = n; - InitMatrix(); - } - - CostType distance(Range s, Range t) - { - auto slen = walkLength(s.save), tlen = walkLength(t.save); - AllocMatrix(slen + 1, tlen + 1); - foreach (i; 1 .. rows) - { - auto sfront = s.front; - s.popFront(); - auto tt = t; - foreach (j; 1 .. cols) - { - auto cSub = matrix(i - 1,j - 1) - + (equals(sfront, tt.front) ? 0 : _substitutionIncrement); - tt.popFront(); - auto cIns = matrix(i,j - 1) + _insertionIncrement; - auto cDel = matrix(i - 1,j) + _deletionIncrement; - switch (min_index(cSub, cIns, cDel)) - { - case 0: - matrix(i,j) = cSub; - break; - case 1: - matrix(i,j) = cIns; - break; - default: - matrix(i,j) = cDel; - break; - } - } - } - return matrix(slen,tlen); - } - - EditOp[] path(Range s, Range t) - { - distanceWithPath(s, t); - return path(); - } - - EditOp[] path() - { - EditOp[] result; - size_t i = rows - 1, j = cols - 1; - // restore the path - while (i || j) { - auto cIns = j == 0 ? CostType.max : matrix(i,j - 1); - auto cDel = i == 0 ? CostType.max : matrix(i - 1,j); - auto cSub = i == 0 || j == 0 - ? CostType.max - : matrix(i - 1,j - 1); - switch (min_index(cSub, cIns, cDel)) { - case 0: - result ~= matrix(i - 1,j - 1) == matrix(i,j) - ? EditOp.none - : EditOp.substitute; - --i; - --j; - break; - case 1: - result ~= EditOp.insert; - --j; - break; - default: - result ~= EditOp.remove; - --i; - break; - } - } - reverse(result); - return result; - } - -private: - CostType _deletionIncrement = 1, - _insertionIncrement = 1, - _substitutionIncrement = 1; - CostType[] _matrix; - size_t rows, cols; - - // Treat _matrix as a rectangular array - ref CostType matrix(size_t row, size_t col) { return _matrix[row * cols + col]; } - - void AllocMatrix(size_t r, size_t c) { - rows = r; - cols = c; - if (_matrix.length < r * c) { - delete _matrix; - _matrix = new CostType[r * c]; - InitMatrix(); - } - } - - void InitMatrix() { - foreach (r; 0 .. rows) - matrix(r,0) = r * _deletionIncrement; - foreach (c; 0 .. cols) - matrix(0,c) = c * _insertionIncrement; - } - - static uint min_index(CostType i0, CostType i1, CostType i2) - { - if (i0 <= i1) - { - return i0 <= i2 ? 0 : 2; - } - else - { - return i1 <= i2 ? 1 : 2; - } - } - - CostType distanceWithPath(Range s, Range t) - { - auto slen = walkLength(s.save), tlen = walkLength(t.save); - AllocMatrix(slen + 1, tlen + 1); - foreach (i; 1 .. rows) - { - auto sfront = s.front; - auto tt = t.save; - foreach (j; 1 .. cols) - { - auto cSub = matrix(i - 1,j - 1) - + (equals(sfront, tt.front) ? 0 : _substitutionIncrement); - tt.popFront(); - auto cIns = matrix(i,j - 1) + _insertionIncrement; - auto cDel = matrix(i - 1,j) + _deletionIncrement; - switch (min_index(cSub, cIns, cDel)) - { - case 0: - matrix(i,j) = cSub; - break; - case 1: - matrix(i,j) = cIns; - break; - default: - matrix(i,j) = cDel; - break; - } - } - s.popFront(); - } - return matrix(slen,tlen); - } - - CostType distanceLowMem(Range s, Range t, CostType slen, CostType tlen) - { - CostType lastdiag, olddiag; - AllocMatrix(slen + 1, 1); - foreach (y; 1 .. slen + 1) - { - _matrix[y] = y; - } - foreach (x; 1 .. tlen + 1) - { - auto tfront = t.front; - auto ss = s.save; - _matrix[0] = x; - lastdiag = x - 1; - foreach (y; 1 .. rows) - { - olddiag = _matrix[y]; - auto cSub = lastdiag + (equals(ss.front, tfront) ? 0 : _substitutionIncrement); - ss.popFront(); - auto cIns = _matrix[y - 1] + _insertionIncrement; - auto cDel = _matrix[y] + _deletionIncrement; - switch (min_index(cSub, cIns, cDel)) - { - case 0: - _matrix[y] = cSub; - break; - case 1: - _matrix[y] = cIns; - break; - default: - _matrix[y] = cDel; - break; - } - lastdiag = olddiag; - } - t.popFront(); - } - return _matrix[slen]; - } -} - -/** -Returns the $(WEB wikipedia.org/wiki/Levenshtein_distance, Levenshtein -distance) between $(D s) and $(D t). The Levenshtein distance computes -the minimal amount of edit operations necessary to transform $(D s) -into $(D t). Performs $(BIGOH s.length * t.length) evaluations of $(D -equals) and occupies $(BIGOH s.length * t.length) storage. - -Allocates GC memory. -*/ -size_t levenshteinDistance(alias equals = "a == b", Range1, Range2) - (Range1 s, Range2 t) - if (isForwardRange!(Range1) && isForwardRange!(Range2)) -{ - Levenshtein!(Range1, binaryFun!(equals), size_t) lev; - auto slen = walkLength(s.save); - auto tlen = walkLength(t.save); - if (slen > tlen) - { - return lev.distanceLowMem(s, t, slen, tlen); - } - else - { - return lev.distanceLowMem(t, s, tlen, slen); - } -} - -/// -unittest -{ - import std.uni : toUpper; - - assert(levenshteinDistance("cat", "rat") == 1); - assert(levenshteinDistance("parks", "spark") == 2); - assert(levenshteinDistance("kitten", "sitting") == 3); - assert(levenshteinDistance!((a, b) => std.uni.toUpper(a) == std.uni.toUpper(b)) - ("parks", "SPARK") == 2); - assert(levenshteinDistance("parks".filter!"true", "spark".filter!"true") == 2); - assert(levenshteinDistance("ID", "I♥D") == 1); -} - -/** -Returns the Levenshtein distance and the edit path between $(D s) and -$(D t). - -Allocates GC memory. -*/ -Tuple!(size_t, EditOp[]) -levenshteinDistanceAndPath(alias equals = "a == b", Range1, Range2) - (Range1 s, Range2 t) - if (isForwardRange!(Range1) && isForwardRange!(Range2)) -{ - Levenshtein!(Range1, binaryFun!(equals)) lev; - auto d = lev.distanceWithPath(s, t); - return tuple(d, lev.path()); -} - -/// -unittest -{ - string a = "Saturday", b = "Sunday"; - auto p = levenshteinDistanceAndPath(a, b); - assert(p[0] == 3); - assert(equal(p[1], "nrrnsnnn")); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - assert(levenshteinDistance("a", "a") == 0); - assert(levenshteinDistance("a", "b") == 1); - assert(levenshteinDistance("aa", "ab") == 1); - assert(levenshteinDistance("aa", "abc") == 2); - assert(levenshteinDistance("Saturday", "Sunday") == 3); - assert(levenshteinDistance("kitten", "sitting") == 3); -} - -// copy -/** -Copies the content of $(D source) into $(D target) and returns the -remaining (unfilled) part of $(D target). - -Preconditions: $(D target) shall have enough room to accomodate -$(D source). - -See_Also: - $(WEB sgi.com/tech/stl/_copy.html, STL's _copy) - */ -Range2 copy(Range1, Range2)(Range1 source, Range2 target) -if (isInputRange!Range1 && isOutputRange!(Range2, ElementType!Range1)) -{ - static Range2 genericImpl(Range1 source, Range2 target) - { - // Specialize for 2 random access ranges. - // Typically 2 random access ranges are faster iterated by common - // index then by x.popFront(), y.popFront() pair - static if (isRandomAccessRange!Range1 && hasLength!Range1 - && hasSlicing!Range2 && isRandomAccessRange!Range2 && hasLength!Range2) - { - assert(target.length >= source.length, - "Cannot copy a source range into a smaller target range."); - - auto len = source.length; - foreach (idx; 0 .. len) - target[idx] = source[idx]; - return target[len .. target.length]; - } - else - { - put(target, source); - return target; - } - } - - static if (isArray!Range1 && isArray!Range2 && - is(Unqual!(typeof(source[0])) == Unqual!(typeof(target[0])))) - { - immutable overlaps = source.ptr < target.ptr + target.length && - target.ptr < source.ptr + source.length; - - if (overlaps) - { - return genericImpl(source, target); - } - else - { - // Array specialization. This uses optimized memory copying - // routines under the hood and is about 10-20x faster than the - // generic implementation. - assert(target.length >= source.length, - "Cannot copy a source array into a smaller target array."); - target[0..source.length] = source[]; - - return target[source.length..$]; - } - } - else - { - return genericImpl(source, target); - } -} - -/// -unittest -{ - int[] a = [ 1, 5 ]; - int[] b = [ 9, 8 ]; - int[] c = new int[a.length + b.length + 10]; - auto d = copy(b, copy(a, c)); - assert(c[0 .. a.length + b.length] == a ~ b); - assert(d.length == 10); -} - -/** -As long as the target range elements support assignment from source -range elements, different types of ranges are accepted. -*/ -unittest -{ - float[] a = [ 1.0f, 5 ]; - double[] b = new double[a.length]; - auto d = copy(a, b); -} - -/** -To copy at most $(D n) elements from range $(D a) to range $(D b), you -may want to use $(D copy(take(a, n), b)). To copy those elements from -range $(D a) that satisfy predicate $(D pred) to range $(D b), you may -want to use $(D copy(a.filter!(pred), b)). -*/ -unittest -{ - int[] a = [ 1, 5, 8, 9, 10, 1, 2, 0 ]; - auto b = new int[a.length]; - auto c = copy(a.filter!(a => (a & 1) == 1), b); - assert(b[0 .. $ - c.length] == [ 1, 5, 9, 1 ]); -} - -/** -$(XREF range, retro) can be used to achieve behavior similar to -$(WEB sgi.com/tech/stl/copy_backward.html, STL's copy_backward'). -*/ -unittest -{ - import std.algorithm, std.range; - int[] src = [1, 2, 4]; - int[] dst = [0, 0, 0, 0, 0]; - copy(src.retro, dst.retro); - assert(dst == [0, 0, 1, 2, 4]); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - { - int[] a = [ 1, 5 ]; - int[] b = [ 9, 8 ]; - auto e = copy(filter!("a > 1")(a), b); - assert(b[0] == 5 && e.length == 1); - } - - { - int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - copy(a[5..10], a[4..9]); - assert(a[4..9] == [6, 7, 8, 9, 10]); - } - - { // Test for bug 7898 - enum v = - { - import std.algorithm; - int[] arr1 = [10, 20, 30, 40, 50]; - int[] arr2 = arr1.dup; - copy(arr1, arr2); - return 35; - }(); - } -} - -// swapRanges -/** -Swaps all elements of $(D r1) with successive elements in $(D r2). -Returns a tuple containing the remainder portions of $(D r1) and $(D -r2) that were not swapped (one of them will be empty). The ranges may -be of different types but must have the same element type and support -swapping. -*/ -Tuple!(Range1, Range2) -swapRanges(Range1, Range2)(Range1 r1, Range2 r2) - if (isInputRange!(Range1) && isInputRange!(Range2) - && hasSwappableElements!(Range1) && hasSwappableElements!(Range2) - && is(ElementType!(Range1) == ElementType!(Range2))) -{ - for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) - { - swap(r1.front, r2.front); - } - return tuple(r1, r2); -} - -/// -unittest -{ - int[] a = [ 100, 101, 102, 103 ]; - int[] b = [ 0, 1, 2, 3 ]; - auto c = swapRanges(a[1 .. 3], b[2 .. 4]); - assert(c[0].empty && c[1].empty); - assert(a == [ 100, 2, 3, 103 ]); - assert(b == [ 0, 1, 101, 102 ]); -} - -// reverse -/** -Reverses $(D r) in-place. Performs $(D r.length / 2) evaluations of $(D -swap). - -See_Also: - $(WEB sgi.com/tech/stl/_reverse.html, STL's _reverse) -*/ -void reverse(Range)(Range r) -if (isBidirectionalRange!Range && !isRandomAccessRange!Range - && hasSwappableElements!Range) -{ - while (!r.empty) - { - swap(r.front, r.back); - r.popFront(); - if (r.empty) break; - r.popBack(); - } -} - -/// -unittest -{ - int[] arr = [ 1, 2, 3 ]; - reverse(arr); - assert(arr == [ 3, 2, 1 ]); -} - -///ditto -void reverse(Range)(Range r) -if (isRandomAccessRange!Range && hasLength!Range) -{ - //swapAt is in fact the only way to swap non lvalue ranges - immutable last = r.length-1; - immutable steps = r.length/2; - for (size_t i = 0; i < steps; i++) - { - swapAt(r, i, last-i); - } -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] range = null; - reverse(range); - range = [ 1 ]; - reverse(range); - assert(range == [1]); - range = [1, 2]; - reverse(range); - assert(range == [2, 1]); - range = [1, 2, 3]; - reverse(range); - assert(range == [3, 2, 1]); -} - -/** -Reverses $(D r) in-place, where $(D r) is a narrow string (having -elements of type $(D char) or $(D wchar)). UTF sequences consisting of -multiple code units are preserved properly. -*/ -void reverse(Char)(Char[] s) -if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable)) -{ - import std.string : representation; - import std.utf : stride; - - auto r = representation(s); - for (size_t i = 0; i < s.length; ) - { - immutable step = std.utf.stride(s, i); - if (step > 1) - { - .reverse(r[i .. i + step]); - i += step; - } - else - { - ++i; - } - } - reverse(r); -} - -/// -unittest -{ - char[] arr = "hello\U00010143\u0100\U00010143".dup; - reverse(arr); - assert(arr == "\U00010143\u0100\U00010143olleh"); -} - -unittest -{ - void test(string a, string b) - { - auto c = a.dup; - reverse(c); - assert(c == b, c ~ " != " ~ b); - } - - test("a", "a"); - test(" ", " "); - test("\u2029", "\u2029"); - test("\u0100", "\u0100"); - test("\u0430", "\u0430"); - test("\U00010143", "\U00010143"); - test("abcdefcdef", "fedcfedcba"); - test("hello\U00010143\u0100\U00010143", "\U00010143\u0100\U00010143olleh"); -} - -/** - The strip group of functions allow stripping of either leading, trailing, - or both leading and trailing elements. - - The $(D stripLeft) function will strip the $(D front) of the range, - the $(D stripRight) function will strip the $(D back) of the range, - while the $(D strip) function will strip both the $(D front) and $(D back) - of the range. - - Note that the $(D strip) and $(D stripRight) functions require the range to - be a $(LREF BidirectionalRange) range. - - All of these functions come in two varieties: one takes a target element, - where the range will be stripped as long as this element can be found. - The other takes a lambda predicate, where the range will be stripped as - long as the predicate returns true. -*/ -Range strip(Range, E)(Range range, E element) - if (isBidirectionalRange!Range && is(typeof(range.front == element) : bool)) -{ - return range.stripLeft(element).stripRight(element); -} - -/// ditto -Range strip(alias pred, Range)(Range range) - if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) -{ - return range.stripLeft!pred().stripRight!pred(); -} - -/// ditto -Range stripLeft(Range, E)(Range range, E element) - if (isInputRange!Range && is(typeof(range.front == element) : bool)) -{ - return find!((auto ref a) => a != element)(range); -} - -/// ditto -Range stripLeft(alias pred, Range)(Range range) - if (isInputRange!Range && is(typeof(pred(range.front)) : bool)) -{ - import std.functional : not; - - return find!(not!pred)(range); -} - -/// ditto -Range stripRight(Range, E)(Range range, E element) - if (isBidirectionalRange!Range && is(typeof(range.back == element) : bool)) -{ - for (; !range.empty; range.popBack()) - { - if (range.back != element) - break; - } - return range; -} - -/// ditto -Range stripRight(alias pred, Range)(Range range) - if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) -{ - for (; !range.empty; range.popBack()) - { - if (!pred(range.back)) - break; - } - return range; -} - -/// Strip leading and trailing elements equal to the target element. -@safe pure unittest -{ - assert(" foobar ".strip(' ') == "foobar"); - assert("00223.444500".strip('0') == "223.4445"); - assert("ëëêéüŗōpéêëë".strip('ë') == "êéüŗōpéê"); - assert([1, 1, 0, 1, 1].strip(1) == [0]); - assert([0.0, 0.01, 0.01, 0.0].strip(0).length == 2); -} - -/// Strip leading and trailing elements while the predicate returns true. -@safe pure unittest -{ - assert(" foobar ".strip!(a => a == ' ')() == "foobar"); - assert("00223.444500".strip!(a => a == '0')() == "223.4445"); - assert("ëëêéüŗōpéêëë".strip!(a => a == 'ë')() == "êéüŗōpéê"); - assert([1, 1, 0, 1, 1].strip!(a => a == 1)() == [0]); - assert([0.0, 0.01, 0.5, 0.6, 0.01, 0.0].strip!(a => a < 0.4)().length == 2); -} - -/// Strip leading elements equal to the target element. -@safe pure unittest -{ - assert(" foobar ".stripLeft(' ') == "foobar "); - assert("00223.444500".stripLeft('0') == "223.444500"); - assert("ůůűniçodêéé".stripLeft('ů') == "űniçodêéé"); - assert([1, 1, 0, 1, 1].stripLeft(1) == [0, 1, 1]); - assert([0.0, 0.01, 0.01, 0.0].stripLeft(0).length == 3); -} - -/// Strip leading elements while the predicate returns true. -@safe pure unittest -{ - assert(" foobar ".stripLeft!(a => a == ' ')() == "foobar "); - assert("00223.444500".stripLeft!(a => a == '0')() == "223.444500"); - assert("ůůűniçodêéé".stripLeft!(a => a == 'ů')() == "űniçodêéé"); - assert([1, 1, 0, 1, 1].stripLeft!(a => a == 1)() == [0, 1, 1]); - assert([0.0, 0.01, 0.10, 0.5, 0.6].stripLeft!(a => a < 0.4)().length == 2); -} - -/// Strip trailing elements equal to the target element. -@safe pure unittest -{ - assert(" foobar ".stripRight(' ') == " foobar"); - assert("00223.444500".stripRight('0') == "00223.4445"); - assert("ùniçodêéé".stripRight('é') == "ùniçodê"); - assert([1, 1, 0, 1, 1].stripRight(1) == [1, 1, 0]); - assert([0.0, 0.01, 0.01, 0.0].stripRight(0).length == 3); -} - -/// Strip trailing elements while the predicate returns true. -@safe pure unittest -{ - assert(" foobar ".stripRight!(a => a == ' ')() == " foobar"); - assert("00223.444500".stripRight!(a => a == '0')() == "00223.4445"); - assert("ùniçodêéé".stripRight!(a => a == 'é')() == "ùniçodê"); - assert([1, 1, 0, 1, 1].stripRight!(a => a == 1)() == [1, 1, 0]); - assert([0.0, 0.01, 0.10, 0.5, 0.6].stripRight!(a => a > 0.4)().length == 3); -} - -// bringToFront -/** -The $(D bringToFront) function has considerable flexibility and -usefulness. It can rotate elements in one buffer left or right, swap -buffers of equal length, and even move elements across disjoint -buffers of different types and different lengths. - -$(D bringToFront) takes two ranges $(D front) and $(D back), which may -be of different types. Considering the concatenation of $(D front) and -$(D back) one unified range, $(D bringToFront) rotates that unified -range such that all elements in $(D back) are brought to the beginning -of the unified range. The relative ordering of elements in $(D front) -and $(D back), respectively, remains unchanged. - -Performs $(BIGOH max(front.length, back.length)) evaluations of $(D -swap). - -Preconditions: - -Either $(D front) and $(D back) are disjoint, or $(D back) is -reachable from $(D front) and $(D front) is not reachable from $(D -back). - -Returns: - -The number of elements brought to the front, i.e., the length of $(D -back). - -See_Also: - $(WEB sgi.com/tech/stl/_rotate.html, STL's rotate) -*/ -size_t bringToFront(Range1, Range2)(Range1 front, Range2 back) - if (isInputRange!Range1 && isForwardRange!Range2) -{ - enum bool sameHeadExists = is(typeof(front.sameHead(back))); - size_t result; - for (bool semidone; !front.empty && !back.empty; ) - { - static if (sameHeadExists) - { - if (front.sameHead(back)) break; // shortcut - } - // Swap elements until front and/or back ends. - auto back0 = back.save; - size_t nswaps; - do - { - static if (sameHeadExists) - { - // Detect the stepping-over condition. - if (front.sameHead(back0)) back0 = back.save; - } - swapFront(front, back); - ++nswaps; - front.popFront(); - back.popFront(); - } - while (!front.empty && !back.empty); - - if (!semidone) result += nswaps; - - // Now deal with the remaining elements. - if (back.empty) - { - if (front.empty) break; - // Right side was shorter, which means that we've brought - // all the back elements to the front. - semidone = true; - // Next pass: bringToFront(front, back0) to adjust the rest. - back = back0; - } - else - { - assert(front.empty); - // Left side was shorter. Let's step into the back. - static if (is(Range1 == Take!Range2)) - { - front = take(back0, nswaps); - } - else - { - immutable subresult = bringToFront(take(back0, nswaps), - back); - if (!semidone) result += subresult; - break; // done - } - } - } - return result; -} - -/** -The simplest use of $(D bringToFront) is for rotating elements in a -buffer. For example: -*/ -unittest -{ - auto arr = [4, 5, 6, 7, 1, 2, 3]; - auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); - assert(p == arr.length - 4); - assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]); -} - -/** -The $(D front) range may actually "step over" the $(D back) -range. This is very useful with forward ranges that cannot compute -comfortably right-bounded subranges like $(D arr[0 .. 4]) above. In -the example below, $(D r2) is a right subrange of $(D r1). -*/ -unittest -{ - import std.container : SList; - - auto list = SList!(int)(4, 5, 6, 7, 1, 2, 3); - auto r1 = list[]; - auto r2 = list[]; popFrontN(r2, 4); - assert(equal(r2, [ 1, 2, 3 ])); - bringToFront(r1, r2); - assert(equal(list[], [ 1, 2, 3, 4, 5, 6, 7 ])); -} - - -/** -Elements can be swapped across ranges of different types: -*/ -unittest -{ - import std.container : SList; - - auto list = SList!(int)(4, 5, 6, 7); - auto vec = [ 1, 2, 3 ]; - bringToFront(list[], vec); - assert(equal(list[], [ 1, 2, 3, 4 ])); - assert(equal(vec, [ 5, 6, 7 ])); -} - -unittest -{ - import std.conv : text; - import std.random : Random, unpredictableSeed, uniform; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - // a more elaborate test - { - auto rnd = Random(unpredictableSeed); - int[] a = new int[uniform(100, 200, rnd)]; - int[] b = new int[uniform(100, 200, rnd)]; - foreach (ref e; a) e = uniform(-100, 100, rnd); - foreach (ref e; b) e = uniform(-100, 100, rnd); - int[] c = a ~ b; - // writeln("a= ", a); - // writeln("b= ", b); - auto n = bringToFront(c[0 .. a.length], c[a.length .. $]); - //writeln("c= ", c); - assert(n == b.length); - assert(c == b ~ a, text(c, "\n", a, "\n", b)); - } - // different types, moveFront, no sameHead - { - static struct R(T) - { - T[] data; - size_t i; - @property - { - R save() { return this; } - bool empty() { return i >= data.length; } - T front() { return data[i]; } - T front(real e) { return data[i] = cast(T) e; } - } - void popFront() { ++i; } - } - auto a = R!int([1, 2, 3, 4, 5]); - auto b = R!real([6, 7, 8, 9]); - auto n = bringToFront(a, b); - assert(n == 4); - assert(a.data == [6, 7, 8, 9, 1]); - assert(b.data == [2, 3, 4, 5]); - } - // front steps over back - { - int[] arr, r1, r2; - - // back is shorter - arr = [4, 5, 6, 7, 1, 2, 3]; - r1 = arr; - r2 = arr[4 .. $]; - bringToFront(r1, r2) == 3 || assert(0); - assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); - - // front is shorter - arr = [5, 6, 7, 1, 2, 3, 4]; - r1 = arr; - r2 = arr[3 .. $]; - bringToFront(r1, r2) == 4 || assert(0); - assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); - } -} - -// SwapStrategy -/** -Defines the swapping strategy for algorithms that need to swap -elements in a range (such as partition and sort). The strategy -concerns the swapping of elements that are not the core concern of the -algorithm. For example, consider an algorithm that sorts $(D [ "abc", -"b", "aBc" ]) according to $(D toUpper(a) < toUpper(b)). That -algorithm might choose to swap the two equivalent strings $(D "abc") -and $(D "aBc"). That does not affect the sorting since both $(D [ -"abc", "aBc", "b" ]) and $(D [ "aBc", "abc", "b" ]) are valid -outcomes. - -Some situations require that the algorithm must NOT ever change the -relative ordering of equivalent elements (in the example above, only -$(D [ "abc", "aBc", "b" ]) would be the correct result). Such -algorithms are called $(B stable). If the ordering algorithm may swap -equivalent elements discretionarily, the ordering is called $(B -unstable). - -Yet another class of algorithms may choose an intermediate tradeoff by -being stable only on a well-defined subrange of the range. There is no -established terminology for such behavior; this library calls it $(B -semistable). - -Generally, the $(D stable) ordering strategy may be more costly in -time and/or space than the other two because it imposes additional -constraints. Similarly, $(D semistable) may be costlier than $(D -unstable). As (semi-)stability is not needed very often, the ordering -algorithms in this module parameterized by $(D SwapStrategy) all -choose $(D SwapStrategy.unstable) as the default. -*/ - -enum SwapStrategy -{ - /** - Allows freely swapping of elements as long as the output - satisfies the algorithm's requirements. - */ - unstable, - /** - In algorithms partitioning ranges in two, preserve relative - ordering of elements only to the left of the partition point. - */ - semistable, - /** - Preserve the relative ordering of elements to the largest - extent allowed by the algorithm's requirements. - */ - stable, -} - -/** -Eliminates elements at given offsets from $(D range) and returns the -shortened range. In the simplest call, one element is removed. - ----- -int[] a = [ 3, 5, 7, 8 ]; -assert(remove(a, 1) == [ 3, 7, 8 ]); -assert(a == [ 3, 7, 8, 8 ]); ----- - -In the case above the element at offset $(D 1) is removed and $(D -remove) returns the range smaller by one element. The original array -has remained of the same length because all functions in $(D -std.algorithm) only change $(I content), not $(I topology). The value -$(D 8) is repeated because $(XREF algorithm, move) was invoked to move -elements around and on integers $(D move) simply copies the source to -the destination. To replace $(D a) with the effect of the removal, -simply assign $(D a = remove(a, 1)). The slice will be rebound to the -shorter array and the operation completes with maximal efficiency. - -Multiple indices can be passed into $(D remove). In that case, -elements at the respective indices are all removed. The indices must -be passed in increasing order, otherwise an exception occurs. - ----- -int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; -assert(remove(a, 1, 3, 5) == - [ 0, 2, 4, 6, 7, 8, 9, 10 ]); ----- - -(Note how all indices refer to slots in the $(I original) array, not -in the array as it is being progressively shortened.) Finally, any -combination of integral offsets and tuples composed of two integral -offsets can be passed in. - ----- -int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; -assert(remove(a, 1, tuple(3, 5), 9) == [ 0, 2, 6, 7, 8, 10 ]); ----- - -In this case, the slots at positions 1, 3, 4, and 9 are removed from -the array. The tuple passes in a range closed to the left and open to -the right (consistent with built-in slices), e.g. $(D tuple(3, 5)) -means indices $(D 3) and $(D 4) but not $(D 5). - -If the need is to remove some elements in the range but the order of -the remaining elements does not have to be preserved, you may want to -pass $(D SwapStrategy.unstable) to $(D remove). - ----- -int[] a = [ 0, 1, 2, 3 ]; -assert(remove!(SwapStrategy.unstable)(a, 1) == [ 0, 3, 2 ]); ----- - -In the case above, the element at slot $(D 1) is removed, but replaced -with the last element of the range. Taking advantage of the relaxation -of the stability requirement, $(D remove) moved elements from the end -of the array over the slots to be removed. This way there is less data -movement to be done which improves the execution time of the function. - -The function $(D remove) works on any forward range. The moving -strategy is (listed from fastest to slowest): $(UL $(LI If $(D s == -SwapStrategy.unstable && isRandomAccessRange!Range && hasLength!Range -&& hasLvalueElements!Range), then elements are moved from the end -of the range into the slots to be filled. In this case, the absolute -minimum of moves is performed.) $(LI Otherwise, if $(D s == -SwapStrategy.unstable && isBidirectionalRange!Range && hasLength!Range -&& hasLvalueElements!Range), then elements are still moved from the -end of the range, but time is spent on advancing between slots by repeated -calls to $(D range.popFront).) $(LI Otherwise, elements are moved -incrementally towards the front of $(D range); a given element is never -moved several times, but more elements are moved than in the previous -cases.)) - */ -Range remove -(SwapStrategy s = SwapStrategy.stable, Range, Offset...) -(Range range, Offset offset) -if (s != SwapStrategy.stable - && isBidirectionalRange!Range - && hasLvalueElements!Range - && hasLength!Range - && Offset.length >= 1) -{ - Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts; - foreach (i, v; offset) - { - static if (is(typeof(v[0]) : size_t) && is(typeof(v[1]) : size_t)) - { - blackouts[i].pos = v[0]; - blackouts[i].len = v[1] - v[0]; - } - else - { - static assert(is(typeof(v) : size_t), typeof(v).stringof); - blackouts[i].pos = v; - blackouts[i].len = 1; - } - static if (i > 0) - { - import std.exception : enforce; - - enforce(blackouts[i - 1].pos + blackouts[i - 1].len - <= blackouts[i].pos, - "remove(): incorrect ordering of elements to remove"); - } - } - - size_t left = 0, right = offset.length - 1; - auto tgt = range.save; - size_t steps = 0; - - while (left <= right) - { - // Look for a blackout on the right - if (blackouts[right].pos + blackouts[right].len >= range.length) - { - range.popBackExactly(blackouts[right].len); - - // Since right is unsigned, we must check for this case, otherwise - // we might turn it into size_t.max and the loop condition will not - // fail when it should. - if (right > 0) - { - --right; - continue; - } - else - break; - } - // Advance to next blackout on the left - assert(blackouts[left].pos >= steps); - tgt.popFrontExactly(blackouts[left].pos - steps); - steps = blackouts[left].pos; - auto toMove = min( - blackouts[left].len, - range.length - (blackouts[right].pos + blackouts[right].len)); - foreach (i; 0 .. toMove) - { - move(range.back, tgt.front); - range.popBack(); - tgt.popFront(); - } - steps += toMove; - if (toMove == blackouts[left].len) - { - // Filled the entire left hole - ++left; - continue; - } - } - - return range; -} - -// Ditto -Range remove -(SwapStrategy s = SwapStrategy.stable, Range, Offset...) -(Range range, Offset offset) -if (s == SwapStrategy.stable - && isBidirectionalRange!Range - && hasLvalueElements!Range - && Offset.length >= 1) -{ - import std.exception : enforce; - - auto result = range; - auto src = range, tgt = range; - size_t pos; - foreach (pass, i; offset) - { - static if (is(typeof(i[0])) && is(typeof(i[1]))) - { - auto from = i[0], delta = i[1] - i[0]; - } - else - { - auto from = i; - enum delta = 1; - } - enforce(pos <= from, - "remove(): incorrect ordering of elements to remove"); - if (pass > 0) - { - for (; pos < from; ++pos, src.popFront(), tgt.popFront()) - { - move(src.front, tgt.front); - } - } - else - { - src.popFrontExactly(from); - tgt.popFrontExactly(from); - pos = from; - } - // now skip source to the "to" position - src.popFrontExactly(delta); - result.popBackExactly(delta); - pos += delta; - } - // leftover move - moveAll(src, tgt); - return result; -} - -unittest -{ - import std.exception : assertThrown; - - // http://d.puremagic.com/issues/show_bug.cgi?id=10173 - int[] test = iota(0, 10).array(); - assertThrown(remove!(SwapStrategy.stable)(test, tuple(2, 4), tuple(1, 3))); - assertThrown(remove!(SwapStrategy.unstable)(test, tuple(2, 4), tuple(1, 3))); - assertThrown(remove!(SwapStrategy.stable)(test, 2, 4, 1, 3)); - assertThrown(remove!(SwapStrategy.unstable)(test, 2, 4, 1, 3)); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - //writeln(remove!(SwapStrategy.stable)(a, 1)); - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - assert(remove!(SwapStrategy.stable)(a, 1) == - [ 0, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]); - - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - assert(remove!(SwapStrategy.unstable)(a, 0, 10) == - [ 9, 1, 2, 3, 4, 5, 6, 7, 8 ]); - - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - assert(remove!(SwapStrategy.unstable)(a, 0, tuple(9, 11)) == - [ 8, 1, 2, 3, 4, 5, 6, 7 ]); - // http://d.puremagic.com/issues/show_bug.cgi?id=5224 - a = [ 1, 2, 3, 4 ]; - assert(remove!(SwapStrategy.unstable)(a, 2) == - [ 1, 2, 4 ]); - - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - //writeln(remove!(SwapStrategy.stable)(a, 1, 5)); - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - assert(remove!(SwapStrategy.stable)(a, 1, 5) == - [ 0, 2, 3, 4, 6, 7, 8, 9, 10 ]); - - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - //writeln(remove!(SwapStrategy.stable)(a, 1, 3, 5)); - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - assert(remove!(SwapStrategy.stable)(a, 1, 3, 5) - == [ 0, 2, 4, 6, 7, 8, 9, 10]); - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - //writeln(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5))); - a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5)) - == [ 0, 2, 5, 6, 7, 8, 9, 10]); - - a = iota(0, 10).array(); - assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7)) - == [0, 9, 8, 7, 4, 5]); -} - -unittest -{ - // Issue 11576 - auto arr = [1,2,3]; - arr = arr.remove!(SwapStrategy.unstable)(2); - assert(arr == [1,2]); - -} - -unittest -{ - // Bug# 12889 - int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]]; - auto orig = arr.dup; - foreach (i; iota(arr.length)) - { - assert(orig == arr.remove!(SwapStrategy.unstable)(tuple(i,i))); - assert(orig == arr.remove!(SwapStrategy.stable)(tuple(i,i))); - } -} - -/** -Reduces the length of the bidirectional range $(D range) by removing -elements that satisfy $(D pred). If $(D s = SwapStrategy.unstable), -elements are moved from the right end of the range over the elements -to eliminate. If $(D s = SwapStrategy.stable) (the default), -elements are moved progressively to front such that their relative -order is preserved. Returns the filtered range. -*/ -Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range) -(Range range) -if (isBidirectionalRange!Range - && hasLvalueElements!Range) -{ - auto result = range; - static if (s != SwapStrategy.stable) - { - for (;!range.empty;) - { - if (!unaryFun!pred(range.front)) - { - range.popFront(); - continue; - } - move(range.back, range.front); - range.popBack(); - result.popBack(); - } - } - else - { - auto tgt = range; - for (; !range.empty; range.popFront()) - { - if (unaryFun!(pred)(range.front)) - { - // yank this guy - result.popBack(); - continue; - } - // keep this guy - move(range.front, tgt.front); - tgt.popFront(); - } - } - return result; -} - -/// -unittest -{ - static immutable base = [1, 2, 3, 2, 4, 2, 5, 2]; - - int[] arr = base[].dup; - - // using a string-based predicate - assert(remove!("a == 2")(arr) == [ 1, 3, 4, 5 ]); - - // The original array contents have been modified, - // so we need to reset it to its original state. - // The length is unmodified however. - arr[] = base[]; - - // using a lambda predicate - assert(remove!(a => a == 2)(arr) == [ 1, 3, 4, 5 ]); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - int[] a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; - assert(remove!("a == 2", SwapStrategy.unstable)(a) == - [ 1, 6, 3, 5, 3, 4, 5 ]); - a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; - //writeln(remove!("a != 2", SwapStrategy.stable)(a)); - assert(remove!("a == 2", SwapStrategy.stable)(a) == - [ 1, 3, 3, 4, 5, 5, 6 ]); -} - -// eliminate -/* * -Reduces $(D r) by overwriting all elements $(D x) that satisfy $(D -pred(x)). Returns the reduced range. - -Example: ----- -int[] arr = [ 1, 2, 3, 4, 5 ]; -// eliminate even elements -auto r = eliminate!("(a & 1) == 0")(arr); -assert(r == [ 1, 3, 5 ]); -assert(arr == [ 1, 3, 5, 4, 5 ]); ----- -*/ -// Range eliminate(alias pred, -// SwapStrategy ss = SwapStrategy.unstable, -// alias move = .move, -// Range)(Range r) -// { -// alias It = Iterator!(Range); -// static void assignIter(It a, It b) { move(*b, *a); } -// return range(begin(r), partitionold!(not!(pred), ss, assignIter, Range)(r)); -// } - -// unittest -// { -// int[] arr = [ 1, 2, 3, 4, 5 ]; -// // eliminate even elements -// auto r = eliminate!("(a & 1) == 0")(arr); -// assert(find!("(a & 1) == 0")(r).empty); -// } - -/* * -Reduces $(D r) by overwriting all elements $(D x) that satisfy $(D -pred(x, v)). Returns the reduced range. - -Example: ----- -int[] arr = [ 1, 2, 3, 2, 4, 5, 2 ]; -// keep elements different from 2 -auto r = eliminate(arr, 2); -assert(r == [ 1, 3, 4, 5 ]); -assert(arr == [ 1, 3, 4, 5, 4, 5, 2 ]); ----- -*/ -// Range eliminate(alias pred = "a == b", -// SwapStrategy ss = SwapStrategy.semistable, -// Range, Value)(Range r, Value v) -// { -// alias It = Iterator!(Range); -// bool comp(typeof(*It) a) { return !binaryFun!(pred)(a, v); } -// static void assignIterB(It a, It b) { *a = *b; } -// return range(begin(r), -// partitionold!(comp, -// ss, assignIterB, Range)(r)); -// } - -// unittest -// { -// int[] arr = [ 1, 2, 3, 2, 4, 5, 2 ]; -// // keep elements different from 2 -// auto r = eliminate(arr, 2); -// assert(r == [ 1, 3, 4, 5 ]); -// assert(arr == [ 1, 3, 4, 5, 4, 5, 2 ]); -// } - -// partition -/** -Partitions a range in two using $(D pred) as a -predicate. Specifically, reorders the range $(D r = [left, -right$(RPAREN)) using $(D swap) such that all elements $(D i) for -which $(D pred(i)) is $(D true) come before all elements $(D j) for -which $(D pred(j)) returns $(D false). - -Performs $(BIGOH r.length) (if unstable or semistable) or $(BIGOH -r.length * log(r.length)) (if stable) evaluations of $(D less) and $(D -swap). The unstable version computes the minimum possible evaluations -of $(D swap) (roughly half of those performed by the semistable -version). - -Returns: - -The right part of $(D r) after partitioning. - -If $(D ss == SwapStrategy.stable), $(D partition) preserves the -relative ordering of all elements $(D a), $(D b) in $(D r) for which -$(D pred(a) == pred(b)). If $(D ss == SwapStrategy.semistable), $(D -partition) preserves the relative ordering of all elements $(D a), $(D -b) in the left part of $(D r) for which $(D pred(a) == pred(b)). - -See_Also: - STL's $(WEB sgi.com/tech/stl/_partition.html, _partition)$(BR) - STL's $(WEB sgi.com/tech/stl/stable_partition.html, stable_partition) -*/ -Range partition(alias predicate, - SwapStrategy ss = SwapStrategy.unstable, Range)(Range r) - if ((ss == SwapStrategy.stable && isRandomAccessRange!(Range)) - || (ss != SwapStrategy.stable && isForwardRange!(Range))) -{ - alias pred = unaryFun!(predicate); - if (r.empty) return r; - static if (ss == SwapStrategy.stable) - { - if (r.length == 1) - { - if (pred(r.front)) r.popFront(); - return r; - } - const middle = r.length / 2; - alias recurse = .partition!(pred, ss, Range); - auto lower = recurse(r[0 .. middle]); - auto upper = recurse(r[middle .. $]); - bringToFront(lower, r[middle .. r.length - upper.length]); - return r[r.length - lower.length - upper.length .. r.length]; - } - else static if (ss == SwapStrategy.semistable) - { - for (; !r.empty; r.popFront()) - { - // skip the initial portion of "correct" elements - if (pred(r.front)) continue; - // hit the first "bad" element - auto result = r; - for (r.popFront(); !r.empty; r.popFront()) - { - if (!pred(r.front)) continue; - swap(result.front, r.front); - result.popFront(); - } - return result; - } - return r; - } - else // ss == SwapStrategy.unstable - { - // Inspired from www.stepanovpapers.com/PAM3-partition_notes.pdf, - // section "Bidirectional Partition Algorithm (Hoare)" - auto result = r; - for (;;) - { - for (;;) - { - if (r.empty) return result; - if (!pred(r.front)) break; - r.popFront(); - result.popFront(); - } - // found the left bound - assert(!r.empty); - for (;;) - { - if (pred(r.back)) break; - r.popBack(); - if (r.empty) return result; - } - // found the right bound, swap & make progress - static if (is(typeof(swap(r.front, r.back)))) - { - swap(r.front, r.back); - } - else - { - auto t1 = moveFront(r), t2 = moveBack(r); - r.front = t2; - r.back = t1; - } - r.popFront(); - result.popFront(); - r.popBack(); - } - } -} - -/// -unittest -{ - import std.conv : text; - - auto Arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - auto arr = Arr.dup; - static bool even(int a) { return (a & 1) == 0; } - // Partition arr such that even numbers come first - auto r = partition!(even)(arr); - // Now arr is separated in evens and odds. - // Numbers may have become shuffled due to instability - assert(r == arr[5 .. $]); - assert(count!(even)(arr[0 .. 5]) == 5); - assert(find!(even)(r).empty); - - // Can also specify the predicate as a string. - // Use 'a' as the predicate argument name - arr[] = Arr[]; - r = partition!(q{(a & 1) == 0})(arr); - assert(r == arr[5 .. $]); - - // Now for a stable partition: - arr[] = Arr[]; - r = partition!(q{(a & 1) == 0}, SwapStrategy.stable)(arr); - // Now arr is [2 4 6 8 10 1 3 5 7 9], and r points to 1 - assert(arr == [2, 4, 6, 8, 10, 1, 3, 5, 7, 9] && r == arr[5 .. $]); - - // In case the predicate needs to hold its own state, use a delegate: - arr[] = Arr[]; - int x = 3; - // Put stuff greater than 3 on the left - bool fun(int a) { return a > x; } - r = partition!(fun, SwapStrategy.semistable)(arr); - // Now arr is [4 5 6 7 8 9 10 2 3 1] and r points to 2 - assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1] && r == arr[7 .. $]); -} - -unittest -{ - static bool even(int a) { return (a & 1) == 0; } - - // test with random data - auto a = rndstuff!int(); - partition!even(a); - assert(isPartitioned!even(a)); - auto b = rndstuff!string(); - partition!`a.length < 5`(b); - assert(isPartitioned!`a.length < 5`(b)); -} - -/** -Returns $(D true) if $(D r) is partitioned according to predicate $(D -pred). - */ -bool isPartitioned(alias pred, Range)(Range r) - if (isForwardRange!(Range)) -{ - for (; !r.empty; r.popFront()) - { - if (unaryFun!(pred)(r.front)) continue; - for (r.popFront(); !r.empty; r.popFront()) - { - if (unaryFun!(pred)(r.front)) return false; - } - break; - } - return true; -} - -/// -unittest -{ - int[] r = [ 1, 3, 5, 7, 8, 2, 4, ]; - assert(isPartitioned!"a & 1"(r)); -} - -// partition3 -/** -Rearranges elements in $(D r) in three adjacent ranges and returns -them. The first and leftmost range only contains elements in $(D r) -less than $(D pivot). The second and middle range only contains -elements in $(D r) that are equal to $(D pivot). Finally, the third -and rightmost range only contains elements in $(D r) that are greater -than $(D pivot). The less-than test is defined by the binary function -$(D less). - -BUGS: stable $(D partition3) has not been implemented yet. - */ -auto partition3(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, E) -(Range r, E pivot) -if (ss == SwapStrategy.unstable && isRandomAccessRange!Range - && hasSwappableElements!Range && hasLength!Range - && is(typeof(binaryFun!less(r.front, pivot)) == bool) - && is(typeof(binaryFun!less(pivot, r.front)) == bool) - && is(typeof(binaryFun!less(r.front, r.front)) == bool)) -{ - // The algorithm is described in "Engineering a sort function" by - // Jon Bentley et al, pp 1257. - - alias lessFun = binaryFun!less; - size_t i, j, k = r.length, l = k; - - bigloop: - for (;;) - { - for (;; ++j) - { - if (j == k) break bigloop; - assert(j < r.length); - if (lessFun(r[j], pivot)) continue; - if (lessFun(pivot, r[j])) break; - swap(r[i++], r[j]); - } - assert(j < k); - for (;;) - { - assert(k > 0); - if (!lessFun(pivot, r[--k])) - { - if (lessFun(r[k], pivot)) break; - swap(r[k], r[--l]); - } - if (j == k) break bigloop; - } - // Here we know r[j] > pivot && r[k] < pivot - swap(r[j++], r[k]); - } - - // Swap the equal ranges from the extremes into the middle - auto strictlyLess = j - i, strictlyGreater = l - k; - auto swapLen = min(i, strictlyLess); - swapRanges(r[0 .. swapLen], r[j - swapLen .. j]); - swapLen = min(r.length - l, strictlyGreater); - swapRanges(r[k .. k + swapLen], r[r.length - swapLen .. r.length]); - return tuple(r[0 .. strictlyLess], - r[strictlyLess .. r.length - strictlyGreater], - r[r.length - strictlyGreater .. r.length]); -} - -/// -unittest -{ - auto a = [ 8, 3, 4, 1, 4, 7, 4 ]; - auto pieces = partition3(a, 4); - assert(pieces[0] == [ 1, 3 ]); - assert(pieces[1] == [ 4, 4, 4 ]); - assert(pieces[2] == [ 8, 7 ]); -} - -unittest -{ - import std.random : uniform; - - auto a = new int[](uniform(0, 100)); - foreach (ref e; a) - { - e = uniform(0, 50); - } - auto pieces = partition3(a, 25); - assert(pieces[0].length + pieces[1].length + pieces[2].length == a.length); - foreach (e; pieces[0]) - { - assert(e < 25); - } - foreach (e; pieces[1]) - { - assert(e == 25); - } - foreach (e; pieces[2]) - { - assert(e > 25); - } -} - -// topN -/** -Reorders the range $(D r) using $(D swap) such that $(D r[nth]) refers -to the element that would fall there if the range were fully -sorted. In addition, it also partitions $(D r) such that all elements -$(D e1) from $(D r[0]) to $(D r[nth]) satisfy $(D !less(r[nth], e1)), -and all elements $(D e2) from $(D r[nth]) to $(D r[r.length]) satisfy -$(D !less(e2, r[nth])). Effectively, it finds the nth smallest -(according to $(D less)) elements in $(D r). Performs an expected -$(BIGOH r.length) (if unstable) or $(BIGOH r.length * log(r.length)) -(if stable) evaluations of $(D less) and $(D swap). - -If $(D n >= r.length), the algorithm has no effect. - -See_Also: - $(WEB sgi.com/tech/stl/nth_element.html, STL's nth_element) - -BUGS: - -Stable topN has not been implemented yet. -*/ -void topN(alias less = "a < b", - SwapStrategy ss = SwapStrategy.unstable, - Range)(Range r, size_t nth) - if (isRandomAccessRange!(Range) && hasLength!Range) -{ - import std.random : uniform; - - static assert(ss == SwapStrategy.unstable, - "Stable topN not yet implemented"); - while (r.length > nth) - { - auto pivot = uniform(0, r.length); - swap(r[pivot], r.back); - assert(!binaryFun!(less)(r.back, r.back)); - auto right = partition!((a) => binaryFun!less(a, r.back), ss)(r); - assert(right.length >= 1); - swap(right.front, r.back); - pivot = r.length - right.length; - if (pivot == nth) - { - return; - } - if (pivot < nth) - { - ++pivot; - r = r[pivot .. $]; - nth -= pivot; - } - else - { - assert(pivot < r.length); - r = r[0 .. pivot]; - } - } -} - -/// -unittest -{ - int[] v = [ 25, 7, 9, 2, 0, 5, 21 ]; - auto n = 4; - topN!"a < b"(v, n); - assert(v[n] == 9); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - //scope(failure) writeln(stderr, "Failure testing algorithm"); - //auto v = ([ 25, 7, 9, 2, 0, 5, 21 ]).dup; - int[] v = [ 7, 6, 5, 4, 3, 2, 1, 0 ]; - ptrdiff_t n = 3; - topN!("a < b")(v, n); - assert(reduce!max(v[0 .. n]) <= v[n]); - assert(reduce!min(v[n + 1 .. $]) >= v[n]); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = 3; - topN(v, n); - assert(reduce!max(v[0 .. n]) <= v[n]); - assert(reduce!min(v[n + 1 .. $]) >= v[n]); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = 1; - topN(v, n); - assert(reduce!max(v[0 .. n]) <= v[n]); - assert(reduce!min(v[n + 1 .. $]) >= v[n]); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = v.length - 1; - topN(v, n); - assert(v[n] == 7); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = 0; - topN(v, n); - assert(v[n] == 1); - - double[][] v1 = [[-10, -5], [-10, -3], [-10, -5], [-10, -4], - [-10, -5], [-9, -5], [-9, -3], [-9, -5],]; - - // double[][] v1 = [ [-10, -5], [-10, -4], [-9, -5], [-9, -5], - // [-10, -5], [-10, -3], [-10, -5], [-9, -3],]; - double[]*[] idx = [ &v1[0], &v1[1], &v1[2], &v1[3], &v1[4], &v1[5], &v1[6], - &v1[7], ]; - - auto mid = v1.length / 2; - topN!((a, b){ return (*a)[1] < (*b)[1]; })(idx, mid); - foreach (e; idx[0 .. mid]) assert((*e)[1] <= (*idx[mid])[1]); - foreach (e; idx[mid .. $]) assert((*e)[1] >= (*idx[mid])[1]); -} - -unittest -{ - import std.random : uniform; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - int[] a = new int[uniform(1, 10000)]; - foreach (ref e; a) e = uniform(-1000, 1000); - auto k = uniform(0, a.length); - topN(a, k); - if (k > 0) - { - auto left = reduce!max(a[0 .. k]); - assert(left <= a[k]); - } - if (k + 1 < a.length) - { - auto right = reduce!min(a[k + 1 .. $]); - assert(right >= a[k]); - } -} - -/** -Stores the smallest elements of the two ranges in the left-hand range. - */ -void topN(alias less = "a < b", - SwapStrategy ss = SwapStrategy.unstable, - Range1, Range2)(Range1 r1, Range2 r2) - if (isRandomAccessRange!(Range1) && hasLength!Range1 && - isInputRange!Range2 && is(ElementType!Range1 == ElementType!Range2)) -{ - import std.container : BinaryHeap; - - static assert(ss == SwapStrategy.unstable, - "Stable topN not yet implemented"); - auto heap = BinaryHeap!Range1(r1); - for (; !r2.empty; r2.popFront()) - { - heap.conditionalInsert(r2.front); - } -} - -/// -unittest -{ - int[] a = [ 5, 7, 2, 6, 7 ]; - int[] b = [ 2, 1, 5, 6, 7, 3, 0 ]; - topN(a, b); - sort(a); - assert(a == [0, 1, 2, 2, 3]); -} - -// sort -/** -Sorts a random-access range according to the predicate $(D less). Performs -$(BIGOH r.length * log(r.length)) evaluations of $(D less). Stable sorting -requires $(D hasAssignableElements!Range) to be true. - -$(D sort) returns a $(XREF range, SortedRange) over the original range, which -functions that can take advantage of sorted data can then use to know that the -range is sorted and adjust accordingly. The $(XREF range, SortedRange) is a -wrapper around the original range, so both it and the original range are sorted, -but other functions won't know that the original range has been sorted, whereas -they $(I can) know that $(XREF range, SortedRange) has been sorted. - -The predicate is expected to satisfy certain rules in order for $(D sort) to -behave as expected - otherwise, the program may fail on certain inputs (but not -others) when not compiled in release mode, due to the cursory $(D assumeSorted) -check. Specifically, $(D sort) expects $(D less(a,b) && less(b,c)) to imply -$(D less(a,c)) (transitivity), and, conversely, $(D !less(a,b) && !less(b,c)) to -imply $(D !less(a,c)). Note that the default predicate ($(D "a < b")) does not -always satisfy these conditions for floating point types, because the expression -will always be $(D false) when either $(D a) or $(D b) is NaN. - -Returns: The initial range wrapped as a $(D SortedRange) with the predicate -$(D binaryFun!less). - -Algorithms: $(WEB en.wikipedia.org/wiki/Introsort) is used for unstable sorting and -$(WEB en.wikipedia.org/wiki/Timsort, Timsort) is used for stable sorting. -Each algorithm has benefits beyond stability. Introsort is generally faster but -Timsort may achieve greater speeds on data with low entropy or if predicate calls -are expensive. Introsort performs no allocations whereas Timsort will perform one -or more allocations per call. Both algorithms have $(BIGOH n log n) worst-case -time complexity. - -See_Also: - $(XREF range, assumeSorted)$(BR) - $(XREF range, SortedRange)$(BR) - $(XREF algorithm, SwapStrategy)$(BR) - $(XREF functional, binaryFun) -*/ - -SortedRange!(Range, less) -sort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, - Range)(Range r) - if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || - hasAssignableElements!Range)) || - (ss != SwapStrategy.unstable && hasAssignableElements!Range)) && - isRandomAccessRange!Range && - hasSlicing!Range && - hasLength!Range) - /+ Unstable sorting uses the quicksort algorithm, which uses swapAt, - which either uses swap(...), requiring swappable elements, or just - swaps using assignment. - Stable sorting uses TimSort, which needs to copy elements into a buffer, - requiring assignable elements. +/ -{ - alias lessFun = binaryFun!(less); - alias LessRet = typeof(lessFun(r.front, r.front)); // instantiate lessFun - static if (is(LessRet == bool)) - { - static if (ss == SwapStrategy.unstable) - quickSortImpl!(lessFun)(r, r.length); - else //use Tim Sort for semistable & stable - TimSortImpl!(lessFun, Range).sort(r, null); - - enum maxLen = 8; - assert(isSorted!lessFun(r), "Failed to sort range of type " ~ Range.stringof); - } - else - { - static assert(false, "Invalid predicate passed to sort: " ~ less.stringof); - } - return assumeSorted!less(r); -} - -/// -@safe pure nothrow unittest -{ - int[] array = [ 1, 2, 3, 4 ]; - // sort in descending order - sort!("a > b")(array); - assert(array == [ 4, 3, 2, 1 ]); - // sort in ascending order - sort(array); - assert(array == [ 1, 2, 3, 4 ]); - // sort with a delegate - bool myComp(int x, int y) @safe pure nothrow { return x > y; } - sort!(myComp)(array); - assert(array == [ 4, 3, 2, 1 ]); -} -/// -unittest -{ - // Showcase stable sorting - string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; - sort!("toUpper(a) < toUpper(b)", SwapStrategy.stable)(words); - assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); -} - -unittest -{ - import std.random : Random, unpredictableSeed, uniform; - import std.string : toUpper; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - // sort using delegate - auto a = new int[100]; - auto rnd = Random(unpredictableSeed); - foreach (ref e; a) { - e = uniform(-100, 100, rnd); - } - - int i = 0; - bool greater2(int a, int b) { return a + i > b + i; } - bool delegate(int, int) greater = &greater2; - sort!(greater)(a); - assert(isSorted!(greater)(a)); - - // sort using string - sort!("a < b")(a); - assert(isSorted!("a < b")(a)); - - // sort using function; all elements equal - foreach (ref e; a) { - e = 5; - } - static bool less(int a, int b) { return a < b; } - sort!(less)(a); - assert(isSorted!(less)(a)); - - string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; - bool lessi(string a, string b) { return toUpper(a) < toUpper(b); } - sort!(lessi, SwapStrategy.stable)(words); - assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); - - // sort using ternary predicate - //sort!("b - a")(a); - //assert(isSorted!(less)(a)); - - a = rndstuff!(int)(); - sort(a); - assert(isSorted(a)); - auto b = rndstuff!(string)(); - sort!("toLower(a) < toLower(b)")(b); - assert(isSorted!("toUpper(a) < toUpper(b)")(b)); - - { - // Issue 10317 - enum E_10317 { a, b } - auto a_10317 = new E_10317[10]; - sort(a_10317); - } - - { - // Issue 7767 - // Unstable sort should complete without an excessive number of predicate calls - // This would suggest it's running in quadratic time - - // Compilation error if predicate is not static, i.e. a nested function - static uint comp; - static bool pred(size_t a, size_t b) - { - ++comp; - return a < b; - } - - size_t[] arr; - arr.length = 1024; - - foreach(k; 0..arr.length) arr[k] = k; - swapRanges(arr[0..$/2], arr[$/2..$]); - - sort!(pred, SwapStrategy.unstable)(arr); - assert(comp < 25_000); - } - - { - bool proxySwapCalled; - struct S - { - int i; - alias i this; - void proxySwap(ref S other) { swap(i, other.i); proxySwapCalled = true; } - @disable void opAssign(S value); - } - - alias R = S[]; - R r = [S(3), S(2), S(1)]; - static assert(hasSwappableElements!R); - static assert(!hasAssignableElements!R); - r.sort(); - assert(proxySwapCalled); - } -} - -private template validPredicates(E, less...) { - static if (less.length == 0) - enum validPredicates = true; - else static if (less.length == 1 && is(typeof(less[0]) == SwapStrategy)) - enum validPredicates = true; - else - enum validPredicates = - is(typeof((E a, E b){ bool r = binaryFun!(less[0])(a, b); })) - && validPredicates!(E, less[1 .. $]); -} - -/** -$(D void multiSort(Range)(Range r) - if (validPredicates!(ElementType!Range, less));) - -Sorts a range by multiple keys. The call $(D multiSort!("a.id < b.id", -"a.date > b.date")(r)) sorts the range $(D r) by $(D id) ascending, -and sorts elements that have the same $(D id) by $(D date) -descending. Such a call is equivalent to $(D sort!"a.id != b.id ? a.id -< b.id : a.date > b.date"(r)), but $(D multiSort) is faster because it -does fewer comparisons (in addition to being more convenient). - */ -template multiSort(less...) //if (less.length > 1) -{ - void multiSort(Range)(Range r) - if (validPredicates!(ElementType!Range, less)) - { - static if (is(typeof(less[$ - 1]) == SwapStrategy)) - { - enum ss = less[$ - 1]; - alias funs = less[0 .. $ - 1]; - } - else - { - alias ss = SwapStrategy.unstable; - alias funs = less; - } - alias lessFun = binaryFun!(funs[0]); - - static if (funs.length > 1) - { - while (r.length > 1) - { - auto p = getPivot!lessFun(r); - auto t = partition3!(less[0], ss)(r, r[p]); - if (t[0].length <= t[2].length) - { - .multiSort!less(t[0]); - .multiSort!(less[1 .. $])(t[1]); - r = t[2]; - } - else - { - .multiSort!(less[1 .. $])(t[1]); - .multiSort!less(t[2]); - r = t[0]; - } - } - } - else - { - sort!(lessFun, ss)(r); - } - } -} - -/// -unittest -{ - static struct Point { int x, y; } - auto pts1 = [ Point(0, 0), Point(5, 5), Point(0, 1), Point(0, 2) ]; - auto pts2 = [ Point(0, 0), Point(0, 1), Point(0, 2), Point(5, 5) ]; - multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1); - assert(pts1 == pts2); -} - -unittest -{ - static struct Point { int x, y; } - auto pts1 = [ Point(5, 6), Point(1, 0), Point(5, 7), Point(1, 1), Point(1, 2), Point(0, 1) ]; - auto pts2 = [ Point(0, 1), Point(1, 0), Point(1, 1), Point(1, 2), Point(5, 6), Point(5, 7) ]; - static assert(validPredicates!(Point, "a.x < b.x", "a.y < b.y")); - multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1); - assert(pts1 == pts2); - - auto pts3 = indexed(pts1, iota(pts1.length)); - multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts3); - assert(equal(pts3, pts2)); -} - -unittest //issue 9160 (L-value only comparators) -{ - static struct A - { - int x; - int y; - } - - static bool byX(const ref A lhs, const ref A rhs) - { - return lhs.x < rhs.x; - } - - static bool byY(const ref A lhs, const ref A rhs) - { - return lhs.y < rhs.y; - } - - auto points = [ A(4, 1), A(2, 4)]; - multiSort!(byX, byY)(points); - assert(points[0] == A(2, 4)); - assert(points[1] == A(4, 1)); -} - -private size_t getPivot(alias less, Range)(Range r) -{ - // This algorithm sorts the first, middle and last elements of r, - // then returns the index of the middle element. In effect, it uses the - // median-of-three heuristic. - - alias pred = binaryFun!(less); - immutable len = r.length; - immutable size_t mid = len / 2; - immutable uint result = ((cast(uint) (pred(r[0], r[mid]))) << 2) | - ((cast(uint) (pred(r[0], r[len - 1]))) << 1) | - (cast(uint) (pred(r[mid], r[len - 1]))); - - switch(result) { - case 0b001: - swapAt(r, 0, len - 1); - swapAt(r, 0, mid); - break; - case 0b110: - swapAt(r, mid, len - 1); - break; - case 0b011: - swapAt(r, 0, mid); - break; - case 0b100: - swapAt(r, mid, len - 1); - swapAt(r, 0, mid); - break; - case 0b000: - swapAt(r, 0, len - 1); - break; - case 0b111: - break; - default: - assert(0); - } - - return mid; -} - -private void optimisticInsertionSort(alias less, Range)(Range r) -{ - alias pred = binaryFun!(less); - if (r.length < 2) - { - return; - } - - immutable maxJ = r.length - 1; - for (size_t i = r.length - 2; i != size_t.max; --i) - { - size_t j = i; - - static if (hasAssignableElements!Range) - { - auto temp = r[i]; - - for (; j < maxJ && pred(r[j + 1], temp); ++j) - { - r[j] = r[j + 1]; - } - - r[j] = temp; - } - else - { - for (; j < maxJ && pred(r[j + 1], r[j]); ++j) - { - swapAt(r, j, j + 1); - } - } - } -} - -unittest -{ - import std.random : Random, uniform; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - auto rnd = Random(1); - auto a = new int[uniform(100, 200, rnd)]; - foreach (ref e; a) { - e = uniform(-100, 100, rnd); - } - - optimisticInsertionSort!(binaryFun!("a < b"), int[])(a); - assert(isSorted(a)); -} - -//private -void swapAt(R)(R r, size_t i1, size_t i2) -{ - static if (is(typeof(&r[i1]))) - { - swap(r[i1], r[i2]); - } - else - { - if (i1 == i2) return; - auto t1 = moveAt(r, i1); - auto t2 = moveAt(r, i2); - r[i2] = t1; - r[i1] = t2; - } -} - -private void quickSortImpl(alias less, Range)(Range r, size_t depth) -{ - alias Elem = ElementType!(Range); - enum size_t optimisticInsertionSortGetsBetter = 25; - static assert(optimisticInsertionSortGetsBetter >= 1); - - // partition - while (r.length > optimisticInsertionSortGetsBetter) - { - if (depth == 0) - { - HeapSortImpl!(less, Range).heapSort(r); - return; - } - depth = depth >= depth.max / 2 ? (depth / 3) * 2 : (depth * 2) / 3; - - const pivotIdx = getPivot!(less)(r); - auto pivot = r[pivotIdx]; - - alias pred = binaryFun!(less); - - // partition - swapAt(r, pivotIdx, r.length - 1); - size_t lessI = size_t.max, greaterI = r.length - 1; - - while (true) - { - while (pred(r[++lessI], pivot)) {} - while (greaterI > 0 && pred(pivot, r[--greaterI])) {} - - if (lessI >= greaterI) - { - break; - } - swapAt(r, lessI, greaterI); - } - - swapAt(r, r.length - 1, lessI); - auto right = r[lessI + 1 .. r.length]; - - auto left = r[0 .. min(lessI, greaterI + 1)]; - if (right.length > left.length) - { - swap(left, right); - } - .quickSortImpl!(less, Range)(right, depth); - r = left; - } - // residual sort - static if (optimisticInsertionSortGetsBetter > 1) - { - optimisticInsertionSort!(less, Range)(r); - } -} - -// Bottom-Up Heap-Sort Implementation -private template HeapSortImpl(alias less, Range) -{ - static assert(isRandomAccessRange!Range); - static assert(hasLength!Range); - static assert(hasSwappableElements!Range || hasAssignableElements!Range); - - alias lessFun = binaryFun!less; - - //template because of @@@12410@@@ - void heapSort()(Range r) - { - // If true, there is nothing to do - if(r.length < 2) return; - - // Build Heap - size_t i = r.length / 2; - while(i > 0) sift(r, --i, r.length); - - // Sort - i = r.length - 1; - while(i > 0) - { - swapAt(r, 0, i); - sift(r, 0, i); - --i; - } - } - - //template because of @@@12410@@@ - void sift()(Range r, size_t parent, immutable size_t end) - { - immutable root = parent; - size_t child = void; - - // Sift down - while(true) - { - child = parent * 2 + 1; - - if(child >= end) break; - - if(child + 1 < end && lessFun(r[child], r[child + 1])) child += 1; - - swapAt(r, parent, child); - parent = child; - } - - child = parent; - - // Sift up - while(child > root) - { - parent = (child - 1) / 2; - if(lessFun(r[parent], r[child])) - { - swapAt(r, parent, child); - child = parent; - } - else break; - } - } -} - -// Tim Sort implementation -private template TimSortImpl(alias pred, R) -{ - import core.bitop : bsr; - - static assert(isRandomAccessRange!R); - static assert(hasLength!R); - static assert(hasSlicing!R); - static assert(hasAssignableElements!R); - - alias T = ElementType!R; - - alias less = binaryFun!pred; - bool greater(T a, T b){ return less(b, a); } - bool greaterEqual(T a, T b){ return !less(a, b); } - bool lessEqual(T a, T b){ return !less(b, a); } - - enum minimalMerge = 128; - enum minimalGallop = 7; - enum minimalStorage = 256; - enum stackSize = 40; - - struct Slice{ size_t base, length; } - - // Entry point for tim sort - void sort(R range, T[] temp) - { - // Do insertion sort on small range - if (range.length <= minimalMerge) - { - binaryInsertionSort(range); - return; - } - - immutable minRun = minRunLength(range.length); - immutable minTemp = min(range.length / 2, minimalStorage); - size_t minGallop = minimalGallop; - Slice[stackSize] stack = void; - size_t stackLen = 0; - - // Allocate temporary memory if not provided by user - if (temp.length < minTemp) - { - if (__ctfe) temp.length = minTemp; - else temp = uninitializedArray!(T[])(minTemp); - } - - for (size_t i = 0; i < range.length; ) - { - // Find length of first run in list - size_t runLen = firstRun(range[i .. range.length]); - - // If run has less than minRun elements, extend using insertion sort - if (runLen < minRun) - { - // Do not run farther than the length of the range - immutable force = range.length - i > minRun ? minRun : range.length - i; - binaryInsertionSort(range[i .. i + force], runLen); - runLen = force; - } - - // Push run onto stack - stack[stackLen++] = Slice(i, runLen); - i += runLen; - - // Collapse stack so that (e1 >= e2 + e3 && e2 >= e3) - // STACK is | ... e1 e2 e3 > - while (stackLen > 1) - { - immutable run3 = stackLen - 1; - immutable run2 = stackLen - 2; - immutable run1 = stackLen - 3; - if (stackLen >= 3 && stack[run1].length <= stack[run2].length + stack[run3].length) - { - immutable at = stack[run1].length <= stack[run3].length - ? run1 : run2; - mergeAt(range, stack[0 .. stackLen], at, minGallop, temp); - --stackLen; - } - else if (stack[run2].length <= stack[run3].length) - { - mergeAt(range, stack[0 .. stackLen], run2, minGallop, temp); - --stackLen; - } - else break; - } - } - - // Force collapse stack until there is only one run left - while (stackLen > 1) - { - immutable run3 = stackLen - 1; - immutable run2 = stackLen - 2; - immutable run1 = stackLen - 3; - immutable at = stackLen >= 3 && stack[run1].length <= stack[run3].length - ? run1 : run2; - mergeAt(range, stack[0 .. stackLen], at, minGallop, temp); - --stackLen; - } - } - - // Calculates optimal value for minRun: - // take first 6 bits of n and add 1 if any lower bits are set - pure size_t minRunLength(size_t n) - { - immutable shift = bsr(n)-5; - auto result = (n>>shift) + !!(n & ~((1<lower; upper--) - range[upper] = moveAt(range, upper-1); - range[lower] = move(item); - } - } - - // Merge two runs in stack (at, at + 1) - void mergeAt(R range, Slice[] stack, immutable size_t at, ref size_t minGallop, ref T[] temp) - in - { - assert(stack.length >= 2); - assert(at == stack.length - 2 || at == stack.length - 3); - } - body - { - immutable base = stack[at].base; - immutable mid = stack[at].length; - immutable len = stack[at + 1].length + mid; - - // Pop run from stack - stack[at] = Slice(base, len); - if (at == stack.length - 3) stack[$ - 2] = stack[$ - 1]; - - // Merge runs (at, at + 1) - return merge(range[base .. base + len], mid, minGallop, temp); - } - - // Merge two runs in a range. Mid is the starting index of the second run. - // minGallop and temp are references; The calling function must receive the updated values. - void merge(R range, size_t mid, ref size_t minGallop, ref T[] temp) - in - { - if (!__ctfe) - { - assert(isSorted!pred(range[0 .. mid])); - assert(isSorted!pred(range[mid .. range.length])); - } - } - body - { - assert(mid < range.length); - - // Reduce range of elements - immutable firstElement = gallopForwardUpper(range[0 .. mid], range[mid]); - immutable lastElement = gallopReverseLower(range[mid .. range.length], range[mid - 1]) + mid; - range = range[firstElement .. lastElement]; - mid -= firstElement; - - if (mid == 0 || mid == range.length) return; - - // Call function which will copy smaller run into temporary memory - if (mid <= range.length / 2) - { - temp = ensureCapacity(mid, temp); - minGallop = mergeLo(range, mid, minGallop, temp); - } - else - { - temp = ensureCapacity(range.length - mid, temp); - minGallop = mergeHi(range, mid, minGallop, temp); - } - } - - // Enlarge size of temporary memory if needed - T[] ensureCapacity(size_t minCapacity, T[] temp) - out(ret) - { - assert(ret.length >= minCapacity); - } - body - { - if (temp.length < minCapacity) - { - size_t newSize = 1<<(bsr(minCapacity)+1); - //Test for overflow - if (newSize < minCapacity) newSize = minCapacity; - - if (__ctfe) temp.length = newSize; - else temp = uninitializedArray!(T[])(newSize); - } - return temp; - } - - // Merge front to back. Returns new value of minGallop. - // temp must be large enough to store range[0 .. mid] - size_t mergeLo(R range, immutable size_t mid, size_t minGallop, T[] temp) - out - { - if (!__ctfe) assert(isSorted!pred(range)); - } - body - { - assert(mid <= range.length); - assert(temp.length >= mid); - - // Copy run into temporary memory - temp = temp[0 .. mid]; - copy(range[0 .. mid], temp); - - // Move first element into place - range[0] = range[mid]; - - size_t i = 1, lef = 0, rig = mid + 1; - size_t count_lef, count_rig; - immutable lef_end = temp.length - 1; - - if (lef < lef_end && rig < range.length) - outer: while(true) - { - count_lef = 0; - count_rig = 0; - - // Linear merge - while ((count_lef | count_rig) < minGallop) - { - if (lessEqual(temp[lef], range[rig])) - { - range[i++] = temp[lef++]; - if(lef >= lef_end) break outer; - ++count_lef; - count_rig = 0; - } - else - { - range[i++] = range[rig++]; - if(rig >= range.length) break outer; - count_lef = 0; - ++count_rig; - } - } - - // Gallop merge - do - { - count_lef = gallopForwardUpper(temp[lef .. $], range[rig]); - foreach (j; 0 .. count_lef) range[i++] = temp[lef++]; - if(lef >= temp.length) break outer; - - count_rig = gallopForwardLower(range[rig .. range.length], temp[lef]); - foreach (j; 0 .. count_rig) range[i++] = range[rig++]; - if (rig >= range.length) while(true) - { - range[i++] = temp[lef++]; - if(lef >= temp.length) break outer; - } - - if (minGallop > 0) --minGallop; - } - while (count_lef >= minimalGallop || count_rig >= minimalGallop); - - minGallop += 2; - } - - // Move remaining elements from right - while (rig < range.length) - range[i++] = range[rig++]; - - // Move remaining elements from left - while (lef < temp.length) - range[i++] = temp[lef++]; - - return minGallop > 0 ? minGallop : 1; - } - - // Merge back to front. Returns new value of minGallop. - // temp must be large enough to store range[mid .. range.length] - size_t mergeHi(R range, immutable size_t mid, size_t minGallop, T[] temp) - out - { - if (!__ctfe) assert(isSorted!pred(range)); - } - body - { - assert(mid <= range.length); - assert(temp.length >= range.length - mid); - - // Copy run into temporary memory - temp = temp[0 .. range.length - mid]; - copy(range[mid .. range.length], temp); - - // Move first element into place - range[range.length - 1] = range[mid - 1]; - - size_t i = range.length - 2, lef = mid - 2, rig = temp.length - 1; - size_t count_lef, count_rig; - - outer: - while(true) - { - count_lef = 0; - count_rig = 0; - - // Linear merge - while((count_lef | count_rig) < minGallop) - { - if(greaterEqual(temp[rig], range[lef])) - { - range[i--] = temp[rig]; - if(rig == 1) - { - // Move remaining elements from left - while(true) - { - range[i--] = range[lef]; - if(lef == 0) break; - --lef; - } - - // Move last element into place - range[i] = temp[0]; - - break outer; - } - --rig; - count_lef = 0; - ++count_rig; - } - else - { - range[i--] = range[lef]; - if(lef == 0) while(true) - { - range[i--] = temp[rig]; - if(rig == 0) break outer; - --rig; - } - --lef; - ++count_lef; - count_rig = 0; - } - } - - // Gallop merge - do - { - count_rig = rig - gallopReverseLower(temp[0 .. rig], range[lef]); - foreach(j; 0 .. count_rig) - { - range[i--] = temp[rig]; - if(rig == 0) break outer; - --rig; - } - - count_lef = lef - gallopReverseUpper(range[0 .. lef], temp[rig]); - foreach(j; 0 .. count_lef) - { - range[i--] = range[lef]; - if(lef == 0) while(true) - { - range[i--] = temp[rig]; - if(rig == 0) break outer; - --rig; - } - --lef; - } - - if(minGallop > 0) --minGallop; - } - while(count_lef >= minimalGallop || count_rig >= minimalGallop); - - minGallop += 2; - } - - return minGallop > 0 ? minGallop : 1; - } - - // false = forward / lower, true = reverse / upper - template gallopSearch(bool forwardReverse, bool lowerUpper) - { - // Gallop search on range according to attributes forwardReverse and lowerUpper - size_t gallopSearch(R)(R range, T value) - out(ret) - { - assert(ret <= range.length); - } - body - { - size_t lower = 0, center = 1, upper = range.length; - alias gap = center; - - static if (forwardReverse) - { - static if (!lowerUpper) alias comp = lessEqual; // reverse lower - static if (lowerUpper) alias comp = less; // reverse upper - - // Gallop Search Reverse - while (gap <= upper) - { - if (comp(value, range[upper - gap])) - { - upper -= gap; - gap *= 2; - } - else - { - lower = upper - gap; - break; - } - } - - // Binary Search Reverse - while (upper != lower) - { - center = lower + (upper - lower) / 2; - if (comp(value, range[center])) upper = center; - else lower = center + 1; - } - } - else - { - static if (!lowerUpper) alias comp = greater; // forward lower - static if (lowerUpper) alias comp = greaterEqual; // forward upper - - // Gallop Search Forward - while (lower + gap < upper) - { - if (comp(value, range[lower + gap])) - { - lower += gap; - gap *= 2; - } - else - { - upper = lower + gap; - break; - } - } - - // Binary Search Forward - while (lower != upper) - { - center = lower + (upper - lower) / 2; - if (comp(value, range[center])) lower = center + 1; - else upper = center; - } - } - - return lower; - } - } - - alias gallopForwardLower = gallopSearch!(false, false); - alias gallopForwardUpper = gallopSearch!(false, true); - alias gallopReverseLower = gallopSearch!( true, false); - alias gallopReverseUpper = gallopSearch!( true, true); -} - -unittest -{ - import std.random : Random, uniform, randomShuffle; - - // Element type with two fields - static struct E - { - size_t value, index; - } - - // Generates data especially for testing sorting with Timsort - static E[] genSampleData(uint seed) - { - auto rnd = Random(seed); - - E[] arr; - arr.length = 64 * 64; - - // We want duplicate values for testing stability - foreach(i, ref v; arr) v.value = i / 64; - - // Swap ranges at random middle point (test large merge operation) - immutable mid = uniform(arr.length / 4, arr.length / 4 * 3, rnd); - swapRanges(arr[0 .. mid], arr[mid .. $]); - - // Shuffle last 1/8 of the array (test insertion sort and linear merge) - randomShuffle(arr[$ / 8 * 7 .. $], rnd); - - // Swap few random elements (test galloping mode) - foreach(i; 0 .. arr.length / 64) - { - immutable a = uniform(0, arr.length, rnd), b = uniform(0, arr.length, rnd); - swap(arr[a], arr[b]); - } - - // Now that our test array is prepped, store original index value - // This will allow us to confirm the array was sorted stably - foreach(i, ref v; arr) v.index = i; - - return arr; - } - - // Tests the Timsort function for correctness and stability - static bool testSort(uint seed) - { - auto arr = genSampleData(seed); - - // Now sort the array! - static bool comp(E a, E b) - { - return a.value < b.value; - } - - sort!(comp, SwapStrategy.stable)(arr); - - // Test that the array was sorted correctly - assert(isSorted!comp(arr)); - - // Test that the array was sorted stably - foreach(i; 0 .. arr.length - 1) - { - if(arr[i].value == arr[i + 1].value) assert(arr[i].index < arr[i + 1].index); - } - - return true; - } - - enum seed = 310614065; - testSort(seed); - - //@@BUG: Timsort fails with CTFE as of DMD 2.060 - // enum result = testSort(seed); -} - -unittest -{//bugzilla 4584 - assert(isSorted!"a hashFun(a) < hashFun(b))(array); -// Sort strings by hash, fast (only computes arr.length hashes): -schwartzSort!(hashFun, "a < b")(array); ----- - -The $(D schwartzSort) function might require less temporary data and -be faster than the Perl idiom or the decorate-sort-undecorate idiom -present in Python and Lisp. This is because sorting is done in-place -and only minimal extra data (one array of transformed elements) is -created. - -To check whether an array was sorted and benefit of the speedup of -Schwartz sorting, a function $(D schwartzIsSorted) is not provided -because the effect can be achieved by calling $(D -isSorted!less(map!transform(r))). - -Returns: The initial range wrapped as a $(D SortedRange) with the -predicate $(D (a, b) => binaryFun!less(transform(a), -transform(b))). - */ -SortedRange!(R, ((a, b) => binaryFun!less(unaryFun!transform(a), - unaryFun!transform(b)))) -schwartzSort(alias transform, alias less = "a < b", - SwapStrategy ss = SwapStrategy.unstable, R)(R r) - if (isRandomAccessRange!R && hasLength!R) -{ - import core.stdc.stdlib : malloc, free; - import std.conv : emplace; - import std.string : representation; - - alias T = typeof(unaryFun!transform(r.front)); - auto xform1 = (cast(T*) malloc(r.length * T.sizeof))[0 .. r.length]; - size_t length; - scope(exit) - { - static if (hasElaborateDestructor!T) - { - foreach (i; 0 .. length) collectException(destroy(xform1[i])); - } - free(xform1.ptr); - } - for (; length != r.length; ++length) - { - emplace(xform1.ptr + length, unaryFun!transform(r[length])); - } - // Make sure we use ubyte[] and ushort[], not char[] and wchar[] - // for the intermediate array, lest zip gets confused. - static if (isNarrowString!(typeof(xform1))) - { - auto xform = xform1.representation(); - } - else - { - alias xform = xform1; - } - zip(xform, r).sort!((a, b) => binaryFun!less(a[0], b[0]), ss)(); - return typeof(return)(r); -} - -unittest -{ - // issue 4909 - Tuple!(char)[] chars; - schwartzSort!"a[0]"(chars); -} - -unittest -{ - // issue 5924 - Tuple!(char)[] chars; - schwartzSort!((Tuple!(char) c){ return c[0]; })(chars); -} - -unittest -{ - import std.math : log2; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - static double entropy(double[] probs) { - double result = 0; - foreach (p; probs) { - if (!p) continue; - //enforce(p > 0 && p <= 1, "Wrong probability passed to entropy"); - result -= p * log2(p); - } - return result; - } - - auto lowEnt = ([ 1.0, 0, 0 ]).dup, - midEnt = ([ 0.1, 0.1, 0.8 ]).dup, - highEnt = ([ 0.31, 0.29, 0.4 ]).dup; - auto arr = new double[][3]; - arr[0] = midEnt; - arr[1] = lowEnt; - arr[2] = highEnt; - - schwartzSort!(entropy, q{a > b})(arr); - assert(arr[0] == highEnt); - assert(arr[1] == midEnt); - assert(arr[2] == lowEnt); - assert(isSorted!("a > b")(map!(entropy)(arr))); -} - -unittest -{ - import std.math : log2; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - static double entropy(double[] probs) { - double result = 0; - foreach (p; probs) { - if (!p) continue; - //enforce(p > 0 && p <= 1, "Wrong probability passed to entropy"); - result -= p * log2(p); - } - return result; - } - - auto lowEnt = ([ 1.0, 0, 0 ]).dup, - midEnt = ([ 0.1, 0.1, 0.8 ]).dup, - highEnt = ([ 0.31, 0.29, 0.4 ]).dup; - auto arr = new double[][3]; - arr[0] = midEnt; - arr[1] = lowEnt; - arr[2] = highEnt; - - schwartzSort!(entropy, q{a < b})(arr); - assert(arr[0] == lowEnt); - assert(arr[1] == midEnt); - assert(arr[2] == highEnt); - assert(isSorted!("a < b")(map!(entropy)(arr))); -} - -// partialSort -/** -Reorders the random-access range $(D r) such that the range $(D r[0 -.. mid]) is the same as if the entire $(D r) were sorted, and leaves -the range $(D r[mid .. r.length]) in no particular order. Performs -$(BIGOH r.length * log(mid)) evaluations of $(D pred). The -implementation simply calls $(D topN!(less, ss)(r, n)) and then $(D -sort!(less, ss)(r[0 .. n])). -*/ -void partialSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, - Range)(Range r, size_t n) - if (isRandomAccessRange!(Range) && hasLength!(Range) && hasSlicing!(Range)) -{ - topN!(less, ss)(r, n); - sort!(less, ss)(r[0 .. n]); -} - -/// -unittest -{ - int[] a = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]; - partialSort(a, 5); - assert(a[0 .. 5] == [ 0, 1, 2, 3, 4 ]); -} - -// completeSort -/** -Sorts the random-access range $(D chain(lhs, rhs)) according to -predicate $(D less). The left-hand side of the range $(D lhs) is -assumed to be already sorted; $(D rhs) is assumed to be unsorted. The -exact strategy chosen depends on the relative sizes of $(D lhs) and -$(D rhs). Performs $(BIGOH lhs.length + rhs.length * log(rhs.length)) -(best case) to $(BIGOH (lhs.length + rhs.length) * log(lhs.length + -rhs.length)) (worst-case) evaluations of $(D swap). -*/ -void completeSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, - Range1, Range2)(SortedRange!(Range1, less) lhs, Range2 rhs) -if (hasLength!(Range2) && hasSlicing!(Range2)) -{ - // Probably this algorithm can be optimized by using in-place - // merge - auto lhsOriginal = lhs.release(); - foreach (i; 0 .. rhs.length) - { - auto sortedSoFar = chain(lhsOriginal, rhs[0 .. i]); - auto ub = assumeSorted!less(sortedSoFar).upperBound(rhs[i]); - if (!ub.length) continue; - bringToFront(ub.release(), rhs[i .. i + 1]); - } -} - -/// -unittest -{ - int[] a = [ 1, 2, 3 ]; - int[] b = [ 4, 0, 6, 5 ]; - completeSort(assumeSorted(a), b); - assert(a == [ 0, 1, 2 ]); - assert(b == [ 3, 4, 5, 6 ]); -} - -// isSorted -/** -Checks whether a forward range is sorted according to the comparison -operation $(D less). Performs $(BIGOH r.length) evaluations of $(D -less). -*/ -bool isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!(Range)) -{ - if (r.empty) return true; - - static if (isRandomAccessRange!Range && hasLength!Range) - { - immutable limit = r.length - 1; - foreach (i; 0 .. limit) - { - if (!binaryFun!less(r[i + 1], r[i])) continue; - assert( - !binaryFun!less(r[i], r[i + 1]), - "Predicate for isSorted is not antisymmetric. Both" ~ - " pred(a, b) and pred(b, a) are true for certain values."); - return false; - } - } - else - { - auto ahead = r; - ahead.popFront(); - size_t i; - - for (; !ahead.empty; ahead.popFront(), r.popFront(), ++i) - { - if (!binaryFun!less(ahead.front, r.front)) continue; - // Check for antisymmetric predicate - assert( - !binaryFun!less(r.front, ahead.front), - "Predicate for isSorted is not antisymmetric. Both" ~ - " pred(a, b) and pred(b, a) are true for certain values."); - return false; - } - } - return true; -} - -/// -unittest -{ - int[] arr = [4, 3, 2, 1]; - assert(!isSorted(arr)); - sort(arr); - assert(isSorted(arr)); - sort!("a > b")(arr); - assert(isSorted!("a > b")(arr)); -} - -unittest -{ - import std.conv : to; - - // Issue 9457 - auto x = "abcd"; - assert(isSorted(x)); - auto y = "acbd"; - assert(!isSorted(y)); - - int[] a = [1, 2, 3]; - assert(isSorted(a)); - int[] b = [1, 3, 2]; - assert(!isSorted(b)); - - dchar[] ds = "コーヒーが好きです"d.dup; - sort(ds); - string s = to!string(ds); - assert(isSorted(ds)); // random-access - assert(isSorted(s)); // bidirectional -} - -// makeIndex -/** -Computes an index for $(D r) based on the comparison $(D less). The -index is a sorted array of pointers or indices into the original -range. This technique is similar to sorting, but it is more flexible -because (1) it allows "sorting" of immutable collections, (2) allows -binary search even if the original collection does not offer random -access, (3) allows multiple indexes, each on a different predicate, -and (4) may be faster when dealing with large objects. However, using -an index may also be slower under certain circumstances due to the -extra indirection, and is always larger than a sorting-based solution -because it needs space for the index in addition to the original -collection. The complexity is the same as $(D sort)'s. - -The first overload of $(D makeIndex) writes to a range containing -pointers, and the second writes to a range containing offsets. The -first overload requires $(D Range) to be a forward range, and the -latter requires it to be a random-access range. - -$(D makeIndex) overwrites its second argument with the result, but -never reallocates it. - -Returns: The pointer-based version returns a $(D SortedRange) wrapper -over index, of type $(D SortedRange!(RangeIndex, (a, b) => -binaryFun!less(*a, *b))) thus reflecting the ordering of the -index. The index-based version returns $(D void) because the ordering -relation involves not only $(D index) but also $(D r). - -Throws: If the second argument's length is less than that of the range -indexed, an exception is thrown. -*/ -SortedRange!(RangeIndex, (a, b) => binaryFun!less(*a, *b)) -makeIndex( - alias less = "a < b", - SwapStrategy ss = SwapStrategy.unstable, - Range, - RangeIndex) -(Range r, RangeIndex index) - if (isForwardRange!(Range) && isRandomAccessRange!(RangeIndex) - && is(ElementType!(RangeIndex) : ElementType!(Range)*)) -{ - import std.exception : enforce; - - // assume collection already ordered - size_t i; - for (; !r.empty; r.popFront(), ++i) - index[i] = addressOf(r.front); - enforce(index.length == i); - // sort the index - sort!((a, b) => binaryFun!less(*a, *b), ss)(index); - return typeof(return)(index); -} - -/// Ditto -void makeIndex( - alias less = "a < b", - SwapStrategy ss = SwapStrategy.unstable, - Range, - RangeIndex) -(Range r, RangeIndex index) -if (isRandomAccessRange!Range && !isInfinite!Range && - isRandomAccessRange!RangeIndex && !isInfinite!RangeIndex && - isIntegral!(ElementType!RangeIndex)) -{ - import std.exception : enforce; - import std.conv : to; - - alias IndexType = Unqual!(ElementType!RangeIndex); - enforce(r.length == index.length, - "r and index must be same length for makeIndex."); - static if (IndexType.sizeof < size_t.sizeof) - { - enforce(r.length <= IndexType.max, "Cannot create an index with " ~ - "element type " ~ IndexType.stringof ~ " with length " ~ - to!string(r.length) ~ "."); - } - - for (IndexType i = 0; i < r.length; ++i) - { - index[cast(size_t) i] = i; - } - - // sort the index - sort!((a, b) => binaryFun!less(r[cast(size_t) a], r[cast(size_t) b]), ss) - (index); -} - -/// -unittest -{ - immutable(int[]) arr = [ 2, 3, 1, 5, 0 ]; - // index using pointers - auto index1 = new immutable(int)*[arr.length]; - makeIndex!("a < b")(arr, index1); - assert(isSorted!("*a < *b")(index1)); - // index using offsets - auto index2 = new size_t[arr.length]; - makeIndex!("a < b")(arr, index2); - assert(isSorted! - ((size_t a, size_t b){ return arr[a] < arr[b];}) - (index2)); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - immutable(int)[] arr = [ 2, 3, 1, 5, 0 ]; - // index using pointers - auto index1 = new immutable(int)*[arr.length]; - alias ImmRange = typeof(arr); - alias ImmIndex = typeof(index1); - static assert(isForwardRange!(ImmRange)); - static assert(isRandomAccessRange!(ImmIndex)); - static assert(!isIntegral!(ElementType!(ImmIndex))); - static assert(is(ElementType!(ImmIndex) : ElementType!(ImmRange)*)); - makeIndex!("a < b")(arr, index1); - assert(isSorted!("*a < *b")(index1)); - - // index using offsets - auto index2 = new long[arr.length]; - makeIndex(arr, index2); - assert(isSorted! - ((long a, long b){ - return arr[cast(size_t) a] < arr[cast(size_t) b]; - })(index2)); - - // index strings using offsets - string[] arr1 = ["I", "have", "no", "chocolate"]; - auto index3 = new byte[arr1.length]; - makeIndex(arr1, index3); - assert(isSorted! - ((byte a, byte b){ return arr1[a] < arr1[b];}) - (index3)); -} - -/** -Specifies whether the output of certain algorithm is desired in sorted -format. - */ -enum SortOutput { - no, /// Don't sort output - yes, /// Sort output -} - -void topNIndex( - alias less = "a < b", - SwapStrategy ss = SwapStrategy.unstable, - Range, RangeIndex)(Range r, RangeIndex index, SortOutput sorted = SortOutput.no) -if (isIntegral!(ElementType!(RangeIndex))) -{ - import std.container : BinaryHeap; - import std.exception : enforce; - - if (index.empty) return; - enforce(ElementType!(RangeIndex).max >= index.length, - "Index type too small"); - bool indirectLess(ElementType!(RangeIndex) a, ElementType!(RangeIndex) b) - { - return binaryFun!(less)(r[a], r[b]); - } - auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); - foreach (i; 0 .. r.length) - { - heap.conditionalInsert(cast(ElementType!RangeIndex) i); - } - if (sorted == SortOutput.yes) - { - while (!heap.empty) heap.removeFront(); - } -} - -void topNIndex( - alias less = "a < b", - SwapStrategy ss = SwapStrategy.unstable, - Range, RangeIndex)(Range r, RangeIndex index, - SortOutput sorted = SortOutput.no) -if (is(ElementType!(RangeIndex) == ElementType!(Range)*)) -{ - import std.container : BinaryHeap; - - if (index.empty) return; - static bool indirectLess(const ElementType!(RangeIndex) a, - const ElementType!(RangeIndex) b) - { - return binaryFun!less(*a, *b); - } - auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); - foreach (i; 0 .. r.length) - { - heap.conditionalInsert(&r[i]); - } - if (sorted == SortOutput.yes) - { - while (!heap.empty) heap.removeFront(); - } -} - -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - { - int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; - int*[] b = new int*[5]; - topNIndex!("a > b")(a, b, SortOutput.yes); - //foreach (e; b) writeln(*e); - assert(b == [ &a[0], &a[2], &a[1], &a[6], &a[5]]); - } - { - int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; - auto b = new ubyte[5]; - topNIndex!("a > b")(a, b, SortOutput.yes); - //foreach (e; b) writeln(e, ":", a[e]); - assert(b == [ cast(ubyte) 0, cast(ubyte)2, cast(ubyte)1, cast(ubyte)6, cast(ubyte)5], text(b)); - } -} -/+ - -// topNIndexImpl -// @@@BUG1904 -/*private*/ void topNIndexImpl( - alias less, - bool sortAfter, - SwapStrategy ss, - SRange, TRange)(SRange source, TRange target) -{ - alias lessFun = binaryFun!(less); - static assert(ss == SwapStrategy.unstable, - "Stable indexing not yet implemented"); - alias SIter = Iterator!(SRange); - alias TElem = std.iterator.ElementType!(TRange); - enum usingInt = isIntegral!(TElem); - - static if (usingInt) - { - enforce(source.length <= TElem.max, - "Numeric overflow at risk in computing topNIndexImpl"); - } - - // types and functions used within - SIter index2iter(TElem a) - { - static if (!usingInt) - return a; - else - return begin(source) + a; - } - bool indirectLess(TElem a, TElem b) - { - return lessFun(*index2iter(a), *index2iter(b)); - } - void indirectCopy(SIter from, ref TElem to) - { - static if (!usingInt) - to = from; - else - to = cast(TElem)(from - begin(source)); - } - - // copy beginning of collection into the target - auto sb = begin(source), se = end(source), - tb = begin(target), te = end(target); - for (; sb != se; ++sb, ++tb) - { - if (tb == te) break; - indirectCopy(sb, *tb); - } - - // if the index's size is same as the source size, just quicksort it - // otherwise, heap-insert stuff in it. - if (sb == se) - { - // everything in source is now in target... just sort the thing - static if (sortAfter) sort!(indirectLess, ss)(target); - } - else - { - // heap-insert - te = tb; - tb = begin(target); - target = range(tb, te); - makeHeap!(indirectLess)(target); - // add stuff to heap - for (; sb != se; ++sb) - { - if (!lessFun(*sb, *index2iter(*tb))) continue; - // copy the source over the smallest - indirectCopy(sb, *tb); - heapify!(indirectLess)(target, tb); - } - static if (sortAfter) sortHeap!(indirectLess)(target); - } -} - -/** -topNIndex -*/ -void topNIndex( - alias less, - SwapStrategy ss = SwapStrategy.unstable, - SRange, TRange)(SRange source, TRange target) -{ - return .topNIndexImpl!(less, false, ss)(source, target); -} - -/// Ditto -void topNIndex( - string less, - SwapStrategy ss = SwapStrategy.unstable, - SRange, TRange)(SRange source, TRange target) -{ - return .topNIndexImpl!(binaryFun!(less), false, ss)(source, target); -} - -// partialIndex -/** -Computes an index for $(D source) based on the comparison $(D less) -and deposits the result in $(D target). It is acceptable that $(D -target.length < source.length), in which case only the smallest $(D -target.length) elements in $(D source) get indexed. The target -provides a sorted "view" into $(D source). This technique is similar -to sorting and partial sorting, but it is more flexible because (1) it -allows "sorting" of immutable collections, (2) allows binary search -even if the original collection does not offer random access, (3) -allows multiple indexes, each on a different comparison criterion, (4) -may be faster when dealing with large objects. However, using an index -may also be slower under certain circumstances due to the extra -indirection, and is always larger than a sorting-based solution -because it needs space for the index in addition to the original -collection. The complexity is $(BIGOH source.length * -log(target.length)). - -Two types of indexes are accepted. They are selected by simply passing -the appropriate $(D target) argument: $(OL $(LI Indexes of type $(D -Iterator!(Source)), in which case the index will be sorted with the -predicate $(D less(*a, *b));) $(LI Indexes of an integral type -(e.g. $(D size_t)), in which case the index will be sorted with the -predicate $(D less(source[a], source[b])).)) - -Example: - ----- -immutable arr = [ 2, 3, 1 ]; -int* index[3]; -partialIndex(arr, index); -assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); -assert(isSorted!("*a < *b")(index)); ----- -*/ -void partialIndex( - alias less, - SwapStrategy ss = SwapStrategy.unstable, - SRange, TRange)(SRange source, TRange target) -{ - return .topNIndexImpl!(less, true, ss)(source, target); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - immutable arr = [ 2, 3, 1 ]; - auto index = new immutable(int)*[3]; - partialIndex!(binaryFun!("a < b"))(arr, index); - assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); - assert(isSorted!("*a < *b")(index)); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - static bool less(int a, int b) { return a < b; } - { - string[] x = ([ "c", "a", "b", "d" ]).dup; - // test with integrals - auto index1 = new size_t[x.length]; - partialIndex!(q{a < b})(x, index1); - assert(index1[0] == 1 && index1[1] == 2 && index1[2] == 0 - && index1[3] == 3); - // half-sized - index1 = new size_t[x.length / 2]; - partialIndex!(q{a < b})(x, index1); - assert(index1[0] == 1 && index1[1] == 2); - - // and with iterators - auto index = new string*[x.length]; - partialIndex!(q{a < b})(x, index); - assert(isSorted!(q{*a < *b})(index)); - assert(*index[0] == "a" && *index[1] == "b" && *index[2] == "c" - && *index[3] == "d"); - } - - { - immutable arr = [ 2, 3, 1 ]; - auto index = new immutable(int)*[arr.length]; - partialIndex!(less)(arr, index); - assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); - assert(isSorted!(q{*a < *b})(index)); - } - - // random data - auto b = rndstuff!(string)(); - auto index = new string*[b.length]; - partialIndex!((a, b) => std.uni.toUpper(a) < std.uni.toUpper(b))(b, index); - assert(isSorted!((a, b) => std.uni.toUpper(*a) < std.uni.toUpper(*b))(index)); - - // random data with indexes - auto index1 = new size_t[b.length]; - bool cmp(string x, string y) { return std.uni.toUpper(x) < std.uni.toUpper(y); } - partialIndex!(cmp)(b, index1); - bool check(size_t x, size_t y) { return std.uni.toUpper(b[x]) < std.uni.toUpper(b[y]); } - assert(isSorted!(check)(index1)); -} - -// Commented out for now, needs reimplementation - -// // schwartzMakeIndex -// /** -// Similar to $(D makeIndex) but using $(D schwartzSort) to sort the -// index. - -// Example: - -// ---- -// string[] arr = [ "ab", "c", "Ab", "C" ]; -// auto index = schwartzMakeIndex!(toUpper, less, SwapStrategy.stable)(arr); -// assert(*index[0] == "ab" && *index[1] == "Ab" -// && *index[2] == "c" && *index[2] == "C"); -// assert(isSorted!("toUpper(*a) < toUpper(*b)")(index)); -// ---- -// */ -// Iterator!(Range)[] schwartzMakeIndex( -// alias transform, -// alias less, -// SwapStrategy ss = SwapStrategy.unstable, -// Range)(Range r) -// { -// alias Iter = Iterator!(Range); -// auto result = new Iter[r.length]; -// // assume collection already ordered -// size_t i = 0; -// foreach (it; begin(r) .. end(r)) -// { -// result[i++] = it; -// } -// // sort the index -// alias Transformed = typeof(transform(*result[0])); -// static bool indirectLess(Transformed a, Transformed b) -// { -// return less(a, b); -// } -// static Transformed indirectTransform(Iter a) -// { -// return transform(*a); -// } -// schwartzSort!(indirectTransform, less, ss)(result); -// return result; -// } - -// /// Ditto -// Iterator!(Range)[] schwartzMakeIndex( -// alias transform, -// string less = q{a < b}, -// SwapStrategy ss = SwapStrategy.unstable, -// Range)(Range r) -// { -// return .schwartzMakeIndex!( -// transform, binaryFun!(less), ss, Range)(r); -// } - -// version (wyda) unittest -// { -// string[] arr = [ "D", "ab", "c", "Ab", "C" ]; -// auto index = schwartzMakeIndex!(toUpper, "a < b", -// SwapStrategy.stable)(arr); -// assert(isSorted!(q{toUpper(*a) < toUpper(*b)})(index)); -// assert(*index[0] == "ab" && *index[1] == "Ab" -// && *index[2] == "c" && *index[3] == "C"); - -// // random data -// auto b = rndstuff!(string)(); -// auto index1 = schwartzMakeIndex!(toUpper)(b); -// assert(isSorted!("toUpper(*a) < toUpper(*b)")(index1)); -// } - -+/ - -// canFind -/++ -Convenience function. Like find, but only returns whether or not the search -was successful. - +/ -template canFind(alias pred="a == b") -{ - /++ - Returns $(D true) if and only if any value $(D v) found in the - input range $(D range) satisfies the predicate $(D pred). - Performs (at most) $(BIGOH haystack.length) evaluations of $(D pred). - +/ - bool canFind(Range)(Range haystack) - if (is(typeof(find!pred(haystack)))) - { - return any!pred(haystack); - } - - /++ - Returns $(D true) if and only if $(D needle) can be found in $(D - range). Performs $(BIGOH haystack.length) evaluations of $(D pred). - +/ - bool canFind(Range, Element)(Range haystack, Element needle) - if (is(typeof(find!pred(haystack, needle)))) - { - return !find!pred(haystack, needle).empty; - } - - /++ - Returns the 1-based index of the first needle found in $(D haystack). If no - needle is found, then $(D 0) is returned. - - So, if used directly in the condition of an if statement or loop, the result - will be $(D true) if one of the needles is found and $(D false) if none are - found, whereas if the result is used elsewhere, it can either be cast to - $(D bool) for the same effect or used to get which needle was found first - without having to deal with the tuple that $(D LREF find) returns for the - same operation. - +/ - size_t canFind(Range, Ranges...)(Range haystack, Ranges needles) - if (Ranges.length > 1 && - allSatisfy!(isForwardRange, Ranges) && - is(typeof(find!pred(haystack, needles)))) - { - return find!pred(haystack, needles)[1]; - } -} - -/// -unittest -{ - assert(canFind([0, 1, 2, 3], 2) == true); - assert(canFind([0, 1, 2, 3], [1, 2], [2, 3])); - assert(canFind([0, 1, 2, 3], [1, 2], [2, 3]) == 1); - assert(canFind([0, 1, 2, 3], [1, 7], [2, 3])); - assert(canFind([0, 1, 2, 3], [1, 7], [2, 3]) == 2); - - assert(canFind([0, 1, 2, 3], 4) == false); - assert(!canFind([0, 1, 2, 3], [1, 3], [2, 4])); - assert(canFind([0, 1, 2, 3], [1, 3], [2, 4]) == 0); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto a = rndstuff!(int)(); - if (a.length) - { - auto b = a[a.length / 2]; - assert(canFind(a, b)); - } -} - -unittest -{ - assert(equal!(canFind!"a < b")([[1, 2, 3], [7, 8, 9]], [2, 8])); -} - -/++ -Checks if $(I _any) of the elements verifies $(D pred). -$(D !any) can be used to verify that $(I none) of the elements verify -$(D pred). - +/ -template any(alias pred = "a") -{ - /++ - Returns $(D true) if and only if $(I _any) value $(D v) found in the - input range $(D range) satisfies the predicate $(D pred). - Performs (at most) $(BIGOH range.length) evaluations of $(D pred). - +/ - bool any(Range)(Range range) - if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) - { - return !find!pred(range).empty; - } -} - -/// -unittest -{ - import std.ascii : isWhite; - assert( all!(any!isWhite)(["a a", "b b"])); - assert(!any!(all!isWhite)(["a a", "b b"])); -} - -/++ -$(D any) can also be used without a predicate, if its items can be -evaluated to true or false in a conditional statement. $(D !any) can be a -convenient way to quickly test that $(I none) of the elements of a range -evaluate to true. - +/ -unittest -{ - int[3] vals1 = [0, 0, 0]; - assert(!any(vals1[])); //none of vals1 evaluate to true - - int[3] vals2 = [2, 0, 2]; - assert( any(vals2[])); - assert(!all(vals2[])); - - int[3] vals3 = [3, 3, 3]; - assert( any(vals3[])); - assert( all(vals3[])); -} - -unittest -{ - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - auto a = [ 1, 2, 0, 4 ]; - assert(any!"a == 2"(a)); -} - -/++ -Checks if $(I _all) of the elements verify $(D pred). - +/ -template all(alias pred = "a") -{ - /++ - Returns $(D true) if and only if $(I _all) values $(D v) found in the - input range $(D range) satisfy the predicate $(D pred). - Performs (at most) $(BIGOH range.length) evaluations of $(D pred). - +/ - bool all(Range)(Range range) - if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) - { - import std.functional : not; - - return find!(not!(unaryFun!pred))(range).empty; - } -} - -/// -unittest -{ - assert( all!"a & 1"([1, 3, 5, 7, 9])); - assert(!all!"a & 1"([1, 2, 3, 5, 7, 9])); -} - -/++ -$(D all) can also be used without a predicate, if its items can be -evaluated to true or false in a conditional statement. This can be a -convenient way to quickly evaluate that $(I _all) of the elements of a range -are true. - +/ -unittest -{ - int[3] vals = [5, 3, 18]; - assert( all(vals[])); -} -unittest -{ - int x = 1; - assert(all!(a => a > x)([2, 3])); -} - -unittest -{ - int x = 1; - assert(all!(a => a > x)([2, 3])); -} - -/** -Copies the top $(D n) elements of the input range $(D source) into the -random-access range $(D target), where $(D n = -target.length). Elements of $(D source) are not touched. If $(D -sorted) is $(D true), the target is sorted. Otherwise, the target -respects the $(WEB en.wikipedia.org/wiki/Binary_heap, heap property). - */ -TRange topNCopy(alias less = "a < b", SRange, TRange) - (SRange source, TRange target, SortOutput sorted = SortOutput.no) - if (isInputRange!(SRange) && isRandomAccessRange!(TRange) - && hasLength!(TRange) && hasSlicing!(TRange)) -{ - import std.container : BinaryHeap; - - if (target.empty) return target; - auto heap = BinaryHeap!(TRange, less)(target, 0); - foreach (e; source) heap.conditionalInsert(e); - auto result = target[0 .. heap.length]; - if (sorted == SortOutput.yes) - { - while (!heap.empty) heap.removeFront(); - } - return result; -} - -/// -unittest -{ - int[] a = [ 10, 16, 2, 3, 1, 5, 0 ]; - int[] b = new int[3]; - topNCopy(a, b, SortOutput.yes); - assert(b == [ 0, 1, 2 ]); -} - -unittest -{ - import std.random : Random, unpredictableSeed, uniform, randomShuffle; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - auto r = Random(unpredictableSeed); - ptrdiff_t[] a = new ptrdiff_t[uniform(1, 1000, r)]; - foreach (i, ref e; a) e = i; - randomShuffle(a, r); - auto n = uniform(0, a.length, r); - ptrdiff_t[] b = new ptrdiff_t[n]; - topNCopy!(binaryFun!("a < b"))(a, b, SortOutput.yes); - assert(isSorted!(binaryFun!("a < b"))(b)); -} - -/** -Lazily computes the union of two or more ranges $(D rs). The ranges -are assumed to be sorted by $(D less). Elements in the output are not -unique; the length of the output is the sum of the lengths of the -inputs. (The $(D length) member is offered if all ranges also have -length.) The element types of all ranges must have a common type. - */ -struct SetUnion(alias less = "a < b", Rs...) if (allSatisfy!(isInputRange, Rs)) -{ -private: - Rs _r; - alias comp = binaryFun!(less); - uint _crt; - - void adjustPosition(uint candidate = 0)() - { - static if (candidate == Rs.length) - { - _crt = _crt.max; - } - else - { - if (_r[candidate].empty) - { - adjustPosition!(candidate + 1)(); - return; - } - foreach (i, U; Rs[candidate + 1 .. $]) - { - enum j = candidate + i + 1; - if (_r[j].empty) continue; - if (comp(_r[j].front, _r[candidate].front)) - { - // a new candidate was found - adjustPosition!(j)(); - return; - } - } - // Found a successful candidate - _crt = candidate; - } - } - -public: - alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); - - this(Rs rs) - { - this._r = rs; - adjustPosition(); - } - - @property bool empty() - { - return _crt == _crt.max; - } - - void popFront() - { - // Assumes _crt is correct - assert(!empty); - foreach (i, U; Rs) - { - if (i < _crt) continue; - // found _crt - assert(!_r[i].empty); - _r[i].popFront(); - adjustPosition(); - return; - } - assert(false); - } - - @property ElementType front() - { - assert(!empty); - // Assume _crt is correct - foreach (i, U; Rs) - { - if (i < _crt) continue; - assert(!_r[i].empty); - return _r[i].front; - } - assert(false); - } - - static if (allSatisfy!(isForwardRange, Rs)) - { - @property auto save() - { - auto ret = this; - foreach (ti, elem; _r) - { - ret._r[ti] = elem.save; - } - return ret; - } - } - - static if (allSatisfy!(hasLength, Rs)) - { - @property size_t length() - { - size_t result; - foreach (i, U; Rs) - { - result += _r[i].length; - } - return result; - } - - alias opDollar = length; - } -} - -/// Ditto -SetUnion!(less, Rs) setUnion(alias less = "a < b", Rs...) -(Rs rs) -{ - return typeof(return)(rs); -} - -/// -unittest -{ - int[] a = [ 1, 2, 4, 5, 7, 9 ]; - int[] b = [ 0, 1, 2, 4, 7, 8 ]; - int[] c = [ 10 ]; - - assert(setUnion(a, b).length == a.length + b.length); - assert(equal(setUnion(a, b), [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9][])); - assert(equal(setUnion(a, c, b), - [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9, 10][])); - - static assert(isForwardRange!(typeof(setUnion(a, b)))); -} - -/** -Lazily computes the intersection of two or more input ranges $(D -ranges). The ranges are assumed to be sorted by $(D less). The element -types of the ranges must have a common type. - */ -struct SetIntersection(alias less = "a < b", Rs...) - if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && - !is(CommonType!(staticMap!(ElementType, Rs)) == void)) -{ -private: - Rs _input; - alias comp = binaryFun!less; - alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); - - // Positions to the first elements that are all equal - void adjustPosition() - { - if (empty) return; - - size_t done = Rs.length; - static if (Rs.length > 1) while (true) - { - foreach (i, ref r; _input) - { - alias next = _input[(i + 1) % Rs.length]; - - if (comp(next.front, r.front)) - { - do { - next.popFront(); - if (next.empty) return; - } while(comp(next.front, r.front)); - done = Rs.length; - } - if (--done == 0) return; - } - } - } - -public: - this(Rs input) - { - this._input = input; - // position to the first element - adjustPosition(); - } - - @property bool empty() - { - foreach (ref r; _input) - { - if (r.empty) return true; - } - return false; - } - - void popFront() - { - assert(!empty); - static if (Rs.length > 1) foreach (i, ref r; _input) - { - alias next = _input[(i + 1) % Rs.length]; - assert(!comp(r.front, next.front)); - } - - foreach (ref r; _input) - { - r.popFront(); - } - adjustPosition(); - } - - @property ElementType front() - { - assert(!empty); - return _input[0].front; - } - - static if (allSatisfy!(isForwardRange, Rs)) - { - @property SetIntersection save() - { - auto ret = this; - foreach (i, ref r; _input) - { - ret._input[i] = r.save; - } - return ret; - } - } -} - -/// Ditto -SetIntersection!(less, Rs) setIntersection(alias less = "a < b", Rs...)(Rs ranges) - if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && - !is(CommonType!(staticMap!(ElementType, Rs)) == void)) -{ - return typeof(return)(ranges); -} - -/// -unittest -{ - int[] a = [ 1, 2, 4, 5, 7, 9 ]; - int[] b = [ 0, 1, 2, 4, 7, 8 ]; - int[] c = [ 0, 1, 4, 5, 7, 8 ]; - assert(equal(setIntersection(a, a), a)); - assert(equal(setIntersection(a, b), [1, 2, 4, 7])); - assert(equal(setIntersection(a, b, c), [1, 4, 7])); -} - -unittest -{ - int[] a = [ 1, 2, 4, 5, 7, 9 ]; - int[] b = [ 0, 1, 2, 4, 7, 8 ]; - int[] c = [ 0, 1, 4, 5, 7, 8 ]; - int[] d = [ 1, 3, 4 ]; - int[] e = [ 4, 5 ]; - - assert(equal(setIntersection(a, a), a)); - assert(equal(setIntersection(a, a, a), a)); - assert(equal(setIntersection(a, b), [1, 2, 4, 7])); - assert(equal(setIntersection(a, b, c), [1, 4, 7])); - assert(equal(setIntersection(a, b, c, d), [1, 4])); - assert(equal(setIntersection(a, b, c, d, e), [4])); - - auto inpA = a.filter!(_ => true), inpB = b.filter!(_ => true); - auto inpC = c.filter!(_ => true), inpD = d.filter!(_ => true); - assert(equal(setIntersection(inpA, inpB, inpC, inpD), [1, 4])); - - assert(equal(setIntersection(a, b, b, a), [1, 2, 4, 7])); - assert(equal(setIntersection(a, c, b), [1, 4, 7])); - assert(equal(setIntersection(b, a, c), [1, 4, 7])); - assert(equal(setIntersection(b, c, a), [1, 4, 7])); - assert(equal(setIntersection(c, a, b), [1, 4, 7])); - assert(equal(setIntersection(c, b, a), [1, 4, 7])); -} - -/** -Lazily computes the difference of $(D r1) and $(D r2). The two ranges -are assumed to be sorted by $(D less). The element types of the two -ranges must have a common type. - */ -struct SetDifference(alias less = "a < b", R1, R2) - if (isInputRange!(R1) && isInputRange!(R2)) -{ -private: - R1 r1; - R2 r2; - alias comp = binaryFun!(less); - - void adjustPosition() - { - while (!r1.empty) - { - if (r2.empty || comp(r1.front, r2.front)) break; - if (comp(r2.front, r1.front)) - { - r2.popFront(); - } - else - { - // both are equal - r1.popFront(); - r2.popFront(); - } - } - } - -public: - this(R1 r1, R2 r2) - { - this.r1 = r1; - this.r2 = r2; - // position to the first element - adjustPosition(); - } - - void popFront() - { - r1.popFront(); - adjustPosition(); - } - - @property auto ref front() - { - assert(!empty); - return r1.front; - } - - static if (isForwardRange!R1 && isForwardRange!R2) - { - @property typeof(this) save() - { - auto ret = this; - ret.r1 = r1.save; - ret.r2 = r2.save; - return ret; - } - } - - @property bool empty() { return r1.empty; } -} - -/// Ditto -SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) -(R1 r1, R2 r2) -{ - return typeof(return)(r1, r2); -} - -/// -unittest -{ - int[] a = [ 1, 2, 4, 5, 7, 9 ]; - int[] b = [ 0, 1, 2, 4, 7, 8 ]; - assert(equal(setDifference(a, b), [5, 9][])); - static assert(isForwardRange!(typeof(setDifference(a, b)))); -} - -unittest // Issue 10460 -{ - int[] a = [1, 2, 3, 4, 5]; - int[] b = [2, 4]; - foreach (ref e; setDifference(a, b)) - e = 0; - assert(equal(a, [0, 2, 0, 4, 0])); -} - -/** -Lazily computes the symmetric difference of $(D r1) and $(D r2), -i.e. the elements that are present in exactly one of $(D r1) and $(D -r2). The two ranges are assumed to be sorted by $(D less), and the -output is also sorted by $(D less). The element types of the two -ranges must have a common type. - -If both arguments are ranges of L-values of the same type then -$(D SetSymmetricDifference) will also be a range of L-values of -that type. - */ -struct SetSymmetricDifference(alias less = "a < b", R1, R2) - if (isInputRange!(R1) && isInputRange!(R2)) -{ -private: - R1 r1; - R2 r2; - //bool usingR2; - alias comp = binaryFun!(less); - - void adjustPosition() - { - while (!r1.empty && !r2.empty) - { - if (comp(r1.front, r2.front) || comp(r2.front, r1.front)) - { - break; - } - // equal, pop both - r1.popFront(); - r2.popFront(); - } - } - -public: - this(R1 r1, R2 r2) - { - this.r1 = r1; - this.r2 = r2; - // position to the first element - adjustPosition(); - } - - void popFront() - { - assert(!empty); - if (r1.empty) r2.popFront(); - else if (r2.empty) r1.popFront(); - else - { - // neither is empty - if (comp(r1.front, r2.front)) - { - r1.popFront(); - } - else - { - assert(comp(r2.front, r1.front)); - r2.popFront(); - } - } - adjustPosition(); - } - - @property auto ref front() - { - assert(!empty); - bool chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front); - assert(chooseR1 || r1.empty || comp(r2.front, r1.front)); - return chooseR1 ? r1.front : r2.front; - } - - static if (isForwardRange!R1 && isForwardRange!R2) - { - @property typeof(this) save() - { - auto ret = this; - ret.r1 = r1.save; - ret.r2 = r2.save; - return ret; - } - } - - ref auto opSlice() { return this; } - - @property bool empty() { return r1.empty && r2.empty; } -} - -/// Ditto -SetSymmetricDifference!(less, R1, R2) -setSymmetricDifference(alias less = "a < b", R1, R2) -(R1 r1, R2 r2) -{ - return typeof(return)(r1, r2); -} - -/// -unittest -{ - int[] a = [ 1, 2, 4, 5, 7, 9 ]; - int[] b = [ 0, 1, 2, 4, 7, 8 ]; - assert(equal(setSymmetricDifference(a, b), [0, 5, 8, 9][])); - static assert(isForwardRange!(typeof(setSymmetricDifference(a, b)))); -} - -unittest // Issue 10460 -{ - int[] a = [1, 2]; - double[] b = [2.0, 3.0]; - int[] c = [2, 3]; - - alias R1 = typeof(setSymmetricDifference(a, b)); - static assert(is(ElementType!R1 == double)); - static assert(!hasLvalueElements!R1); - - alias R2 = typeof(setSymmetricDifference(a, c)); - static assert(is(ElementType!R2 == int)); - static assert(hasLvalueElements!R2); -} - -// Internal random array generators -version(unittest) -{ - private enum size_t maxArraySize = 50; - private enum size_t minArraySize = maxArraySize - 1; - - private string[] rndstuff(T : string)() - { - import std.random : Random, unpredictableSeed, uniform; - - static Random rnd; - static bool first = true; - if (first) - { - rnd = Random(unpredictableSeed); - first = false; - } - string[] result = - new string[uniform(minArraySize, maxArraySize, rnd)]; - string alpha = "abcdefghijABCDEFGHIJ"; - foreach (ref s; result) - { - foreach (i; 0 .. uniform(0u, 20u, rnd)) - { - auto j = uniform(0, alpha.length - 1, rnd); - s ~= alpha[j]; - } - } - return result; - } - - private int[] rndstuff(T : int)() - { - import std.random : Random, unpredictableSeed, uniform; - - static Random rnd; - static bool first = true; - if (first) - { - rnd = Random(unpredictableSeed); - first = false; - } - int[] result = new int[uniform(minArraySize, maxArraySize, rnd)]; - foreach (ref i; result) - { - i = uniform(-100, 100, rnd); - } - return result; - } - - private double[] rndstuff(T : double)() - { - double[] result; - foreach (i; rndstuff!(int)()) - { - result ~= i / 50.0; - } - return result; - } - - //Reference type input range - private class ReferenceInputRange(T) - { - this(Range)(Range r) if (isInputRange!Range) {_payload = array(r);} - final @property ref T front(){return _payload.front;} - final void popFront(){_payload.popFront();} - final @property bool empty(){return _payload.empty;} - protected T[] _payload; - } - - //Reference forward range - private class ReferenceForwardRange(T) : ReferenceInputRange!T - { - this(Range)(Range r) if (isInputRange!Range) {super(r);} - final @property ReferenceForwardRange save() - {return new ReferenceForwardRange!T(_payload);} - } - - //Infinite input range - private class ReferenceInfiniteInputRange(T) - { - this(T first = T.init) {_val = first;} - final @property T front(){return _val;} - final void popFront(){++_val;} - enum bool empty = false; - protected T _val; - } - - //Infinite forward range - private class ReferenceInfiniteForwardRange(T) : ReferenceInfiniteInputRange!T - { - this(T first = T.init) {super(first);} - final @property ReferenceInfiniteForwardRange save() - {return new ReferenceInfiniteForwardRange!T(_val);} - } -} - -// NWayUnion -/** -Computes the union of multiple sets. The input sets are passed as a -range of ranges and each is assumed to be sorted by $(D -less). Computation is done lazily, one union element at a time. The -complexity of one $(D popFront) operation is $(BIGOH -log(ror.length)). However, the length of $(D ror) decreases as ranges -in it are exhausted, so the complexity of a full pass through $(D -NWayUnion) is dependent on the distribution of the lengths of ranges -contained within $(D ror). If all ranges have the same length $(D n) -(worst case scenario), the complexity of a full pass through $(D -NWayUnion) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D -log(ror.length)) times worse than just spanning all ranges in -turn. The output comes sorted (unstably) by $(D less). - -Warning: Because $(D NWayUnion) does not allocate extra memory, it -will leave $(D ror) modified. Namely, $(D NWayUnion) assumes ownership -of $(D ror) and discretionarily swaps and advances elements of it. If -you want $(D ror) to preserve its contents after the call, you may -want to pass a duplicate to $(D NWayUnion) (and perhaps cache the -duplicate in between calls). - */ -struct NWayUnion(alias less, RangeOfRanges) -{ - import std.container : BinaryHeap; - - private alias ElementType = .ElementType!(.ElementType!RangeOfRanges); - private alias comp = binaryFun!less; - private RangeOfRanges _ror; - static bool compFront(.ElementType!RangeOfRanges a, - .ElementType!RangeOfRanges b) - { - // revert comparison order so we get the smallest elements first - return comp(b.front, a.front); - } - BinaryHeap!(RangeOfRanges, compFront) _heap; - - this(RangeOfRanges ror) - { - // Preemptively get rid of all empty ranges in the input - // No need for stability either - _ror = remove!("a.empty", SwapStrategy.unstable)(ror); - //Build the heap across the range - _heap.acquire(_ror); - } - - @property bool empty() { return _ror.empty; } - - @property auto ref front() - { - return _heap.front.front; - } - - void popFront() - { - _heap.removeFront(); - // let's look at the guy just popped - _ror.back.popFront(); - if (_ror.back.empty) - { - _ror.popBack(); - // nothing else to do: the empty range is not in the - // heap and not in _ror - return; - } - // Put the popped range back in the heap - _heap.conditionalInsert(_ror.back) || assert(false); - } -} - -/// Ditto -NWayUnion!(less, RangeOfRanges) nWayUnion -(alias less = "a < b", RangeOfRanges) -(RangeOfRanges ror) -{ - return typeof(return)(ror); -} - -/// -unittest -{ - double[][] a = - [ - [ 1, 4, 7, 8 ], - [ 1, 7 ], - [ 1, 7, 8], - [ 4 ], - [ 7 ], - ]; - auto witness = [ - 1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8 - ]; - assert(equal(nWayUnion(a), witness)); -} - -// largestPartialIntersection -/** -Given a range of sorted forward ranges $(D ror), copies to $(D tgt) -the elements that are common to most ranges, along with their number -of occurrences. All ranges in $(D ror) are assumed to be sorted by $(D -less). Only the most frequent $(D tgt.length) elements are returned. - -Example: ----- -// Figure which number can be found in most arrays of the set of -// arrays below. -double[][] a = -[ - [ 1, 4, 7, 8 ], - [ 1, 7 ], - [ 1, 7, 8], - [ 4 ], - [ 7 ], -]; -auto b = new Tuple!(double, uint)[1]; -largestPartialIntersection(a, b); -// First member is the item, second is the occurrence count -assert(b[0] == tuple(7.0, 4u)); ----- - -$(D 7.0) is the correct answer because it occurs in $(D 4) out of the -$(D 5) inputs, more than any other number. The second member of the -resulting tuple is indeed $(D 4) (recording the number of occurrences -of $(D 7.0)). If more of the top-frequent numbers are needed, just -create a larger $(D tgt) range. In the example above, creating $(D b) -with length $(D 2) yields $(D tuple(1.0, 3u)) in the second position. - -The function $(D largestPartialIntersection) is useful for -e.g. searching an $(LUCKY inverted index) for the documents most -likely to contain some terms of interest. The complexity of the search -is $(BIGOH n * log(tgt.length)), where $(D n) is the sum of lengths of -all input ranges. This approach is faster than keeping an associative -array of the occurrences and then selecting its top items, and also -requires less memory ($(D largestPartialIntersection) builds its -result directly in $(D tgt) and requires no extra memory). - -Warning: Because $(D largestPartialIntersection) does not allocate -extra memory, it will leave $(D ror) modified. Namely, $(D -largestPartialIntersection) assumes ownership of $(D ror) and -discretionarily swaps and advances elements of it. If you want $(D -ror) to preserve its contents after the call, you may want to pass a -duplicate to $(D largestPartialIntersection) (and perhaps cache the -duplicate in between calls). - */ -void largestPartialIntersection -(alias less = "a < b", RangeOfRanges, Range) -(RangeOfRanges ror, Range tgt, SortOutput sorted = SortOutput.no) -{ - struct UnitWeights - { - static int opIndex(ElementType!(ElementType!RangeOfRanges)) { return 1; } - } - return largestPartialIntersectionWeighted!less(ror, tgt, UnitWeights(), - sorted); -} - -// largestPartialIntersectionWeighted -/** -Similar to $(D largestPartialIntersection), but associates a weight -with each distinct element in the intersection. - -Example: ----- -// Figure which number can be found in most arrays of the set of -// arrays below, with specific per-element weights -double[][] a = -[ - [ 1, 4, 7, 8 ], - [ 1, 7 ], - [ 1, 7, 8], - [ 4 ], - [ 7 ], -]; -auto b = new Tuple!(double, uint)[1]; -double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; -largestPartialIntersectionWeighted(a, b, weights); -// First member is the item, second is the occurrence count -assert(b[0] == tuple(4.0, 2u)); ----- - -The correct answer in this case is $(D 4.0), which, although only -appears two times, has a total weight $(D 4.6) (three times its weight -$(D 2.3)). The value $(D 7) is weighted with $(D 1.1) and occurs four -times for a total weight $(D 4.4). - */ -void largestPartialIntersectionWeighted -(alias less = "a < b", RangeOfRanges, Range, WeightsAA) -(RangeOfRanges ror, Range tgt, WeightsAA weights, SortOutput sorted = SortOutput.no) -{ - if (tgt.empty) return; - alias InfoType = ElementType!Range; - bool heapComp(InfoType a, InfoType b) - { - return weights[a[0]] * a[1] > weights[b[0]] * b[1]; - } - topNCopy!heapComp(group(nWayUnion!less(ror)), tgt, sorted); -} - -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - double[][] a = - [ - [ 1, 4, 7, 8 ], - [ 1, 7 ], - [ 1, 7, 8], - [ 4 ], - [ 7 ], - ]; - auto b = new Tuple!(double, uint)[2]; - largestPartialIntersection(a, b, SortOutput.yes); - //sort(b); - //writeln(b); - assert(b == [ tuple(7.0, 4u), tuple(1.0, 3u) ][], text(b)); - assert(a[0].empty); -} - -unittest -{ - import std.conv : text; - - debug(std_algorithm) scope(success) - writeln("unittest @", __FILE__, ":", __LINE__, " done."); - - string[][] a = - [ - [ "1", "4", "7", "8" ], - [ "1", "7" ], - [ "1", "7", "8"], - [ "4" ], - [ "7" ], - ]; - auto b = new Tuple!(string, uint)[2]; - largestPartialIntersection(a, b, SortOutput.yes); - //writeln(b); - assert(b == [ tuple("7", 4u), tuple("1", 3u) ][], text(b)); -} - -unittest -{ - //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); -// Figure which number can be found in most arrays of the set of -// arrays below, with specific per-element weights - double[][] a = - [ - [ 1, 4, 7, 8 ], - [ 1, 7 ], - [ 1, 7, 8], - [ 4 ], - [ 7 ], - ]; - auto b = new Tuple!(double, uint)[1]; - double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; - largestPartialIntersectionWeighted(a, b, weights); -// First member is the item, second is the occurrence count - //writeln(b[0]); - assert(b[0] == tuple(4.0, 2u)); -} - -unittest -{ - import std.container : Array; - - alias T = Tuple!(uint, uint); - const Array!T arrayOne = Array!T( [ T(1,2), T(3,4) ] ); - const Array!T arrayTwo = Array!T([ T(1,2), T(3,4) ] ); - - assert(arrayOne == arrayTwo); -} - -// nextPermutation -/** - * Permutes $(D range) in-place to the next lexicographically greater - * permutation. - * - * The predicate $(D less) defines the lexicographical ordering to be used on - * the range. - * - * If the range is currently the lexicographically greatest permutation, it is - * permuted back to the least permutation and false is returned. Otherwise, - * true is returned. One can thus generate all permutations of a range by - * sorting it according to $(D less), which produces the lexicographically - * least permutation, and then calling nextPermutation until it returns false. - * This is guaranteed to generate all distinct permutations of the range - * exactly once. If there are $(I N) elements in the range and all of them are - * unique, then $(I N)! permutations will be generated. Otherwise, if there are - * some duplicated elements, fewer permutations will be produced. ----- -// Enumerate all permutations -int[] a = [1,2,3,4,5]; -do -{ - // use the current permutation and - // proceed to the next permutation of the array. -} while (nextPermutation(a)); ----- - * Returns: false if the range was lexicographically the greatest, in which - * case the range is reversed back to the lexicographically smallest - * permutation; otherwise returns true. - */ -bool nextPermutation(alias less="a binaryFun!less(i.front, a))( - takeExactly(retro(range), n)); - - assert(!j.empty); // shouldn't happen since i.front < last.front - swap(i.front, j.front); - reverse(takeExactly(retro(range), n)); - - return true; -} - -/// -unittest -{ - // Step through all permutations of a sorted array in lexicographic order - int[] a = [1,2,3]; - assert(nextPermutation(a) == true); - assert(a == [1,3,2]); - assert(nextPermutation(a) == true); - assert(a == [2,1,3]); - assert(nextPermutation(a) == true); - assert(a == [2,3,1]); - assert(nextPermutation(a) == true); - assert(a == [3,1,2]); - assert(nextPermutation(a) == true); - assert(a == [3,2,1]); - assert(nextPermutation(a) == false); - assert(a == [1,2,3]); -} - -/// -unittest -{ - // Step through permutations of an array containing duplicate elements: - int[] a = [1,1,2]; - assert(nextPermutation(a) == true); - assert(a == [1,2,1]); - assert(nextPermutation(a) == true); - assert(a == [2,1,1]); - assert(nextPermutation(a) == false); - assert(a == [1,1,2]); -} - -unittest -{ - // Boundary cases: arrays of 0 or 1 element. - int[] a1 = []; - assert(!nextPermutation(a1)); - assert(a1 == []); - - int[] a2 = [1]; - assert(!nextPermutation(a2)); - assert(a2 == [1]); -} - -unittest -{ - auto a1 = [1, 2, 3, 4]; - - assert(nextPermutation(a1)); - assert(equal(a1, [1, 2, 4, 3])); - - assert(nextPermutation(a1)); - assert(equal(a1, [1, 3, 2, 4])); - - assert(nextPermutation(a1)); - assert(equal(a1, [1, 3, 4, 2])); - - assert(nextPermutation(a1)); - assert(equal(a1, [1, 4, 2, 3])); - - assert(nextPermutation(a1)); - assert(equal(a1, [1, 4, 3, 2])); - - assert(nextPermutation(a1)); - assert(equal(a1, [2, 1, 3, 4])); - - assert(nextPermutation(a1)); - assert(equal(a1, [2, 1, 4, 3])); - - assert(nextPermutation(a1)); - assert(equal(a1, [2, 3, 1, 4])); - - assert(nextPermutation(a1)); - assert(equal(a1, [2, 3, 4, 1])); - - assert(nextPermutation(a1)); - assert(equal(a1, [2, 4, 1, 3])); - - assert(nextPermutation(a1)); - assert(equal(a1, [2, 4, 3, 1])); - - assert(nextPermutation(a1)); - assert(equal(a1, [3, 1, 2, 4])); - - assert(nextPermutation(a1)); - assert(equal(a1, [3, 1, 4, 2])); - - assert(nextPermutation(a1)); - assert(equal(a1, [3, 2, 1, 4])); - - assert(nextPermutation(a1)); - assert(equal(a1, [3, 2, 4, 1])); - - assert(nextPermutation(a1)); - assert(equal(a1, [3, 4, 1, 2])); - - assert(nextPermutation(a1)); - assert(equal(a1, [3, 4, 2, 1])); - - assert(nextPermutation(a1)); - assert(equal(a1, [4, 1, 2, 3])); - - assert(nextPermutation(a1)); - assert(equal(a1, [4, 1, 3, 2])); - - assert(nextPermutation(a1)); - assert(equal(a1, [4, 2, 1, 3])); - - assert(nextPermutation(a1)); - assert(equal(a1, [4, 2, 3, 1])); - - assert(nextPermutation(a1)); - assert(equal(a1, [4, 3, 1, 2])); - - assert(nextPermutation(a1)); - assert(equal(a1, [4, 3, 2, 1])); - - assert(!nextPermutation(a1)); - assert(equal(a1, [1, 2, 3, 4])); -} - -unittest -{ - // Test with non-default sorting order - int[] a = [3,2,1]; - assert(nextPermutation!"a > b"(a) == true); - assert(a == [3,1,2]); - assert(nextPermutation!"a > b"(a) == true); - assert(a == [2,3,1]); - assert(nextPermutation!"a > b"(a) == true); - assert(a == [2,1,3]); - assert(nextPermutation!"a > b"(a) == true); - assert(a == [1,3,2]); - assert(nextPermutation!"a > b"(a) == true); - assert(a == [1,2,3]); - assert(nextPermutation!"a > b"(a) == false); - assert(a == [3,2,1]); -} - -// nextEvenPermutation -/** - * Permutes $(D range) in-place to the next lexicographically greater $(I even) - * permutation. - * - * The predicate $(D less) defines the lexicographical ordering to be used on - * the range. - * - * An even permutation is one which is produced by swapping an even number of - * pairs of elements in the original range. The set of $(I even) permutations - * is distinct from the set of $(I all) permutations only when there are no - * duplicate elements in the range. If the range has $(I N) unique elements, - * then there are exactly $(I N)!/2 even permutations. - * - * If the range is already the lexicographically greatest even permutation, it - * is permuted back to the least even permutation and false is returned. - * Otherwise, true is returned, and the range is modified in-place to be the - * lexicographically next even permutation. - * - * One can thus generate the even permutations of a range with unique elements - * by starting with the lexicographically smallest permutation, and repeatedly - * calling nextEvenPermutation until it returns false. ----- -// Enumerate even permutations -int[] a = [1,2,3,4,5]; -do -{ - // use the current permutation and - // proceed to the next even permutation of the array. -} while (nextEvenPermutation(a)); ----- - * One can also generate the $(I odd) permutations of a range by noting that - * permutations obey the rule that even + even = even, and odd + even = odd. - * Thus, by swapping the last two elements of a lexicographically least range, - * it is turned into the first odd permutation. Then calling - * nextEvenPermutation on this first odd permutation will generate the next - * even permutation relative to this odd permutation, which is actually the - * next odd permutation of the original range. Thus, by repeatedly calling - * nextEvenPermutation until it returns false, one enumerates the odd - * permutations of the original range. ----- -// Enumerate odd permutations -int[] a = [1,2,3,4,5]; -swap(a[$-2], a[$-1]); // a is now the first odd permutation of [1,2,3,4,5] -do -{ - // use the current permutation and - // proceed to the next odd permutation of the original array - // (which is an even permutation of the first odd permutation). -} while (nextEvenPermutation(a)); ----- - * - * Warning: Since even permutations are only distinct from all permutations - * when the range elements are unique, this function assumes that there are no - * duplicate elements under the specified ordering. If this is not _true, some - * permutations may fail to be generated. When the range has non-unique - * elements, you should use $(MYREF nextPermutation) instead. - * - * Returns: false if the range was lexicographically the greatest, in which - * case the range is reversed back to the lexicographically smallest - * permutation; otherwise returns true. - */ -bool nextEvenPermutation(alias less="a binaryFun!less(i.front, a))( - takeExactly(retro(range), n)); - - // shouldn't happen since i.front < last.front - assert(!j.empty); - - swap(i.front, j.front); - oddParity = !oddParity; - } - else - { - // Entire range is decreasing: it's lexicographically - // the greatest. - ret = false; - } - - reverse(takeExactly(retro(range), n)); - if ((n / 2) % 2 == 1) - oddParity = !oddParity; - } while(oddParity); - - return ret; -} - -/// -unittest -{ - // Step through even permutations of a sorted array in lexicographic order - int[] a = [1,2,3]; - assert(nextEvenPermutation(a) == true); - assert(a == [2,3,1]); - assert(nextEvenPermutation(a) == true); - assert(a == [3,1,2]); - assert(nextEvenPermutation(a) == false); - assert(a == [1,2,3]); -} - -unittest -{ - auto a3 = [ 1, 2, 3, 4 ]; - int count = 1; - while (nextEvenPermutation(a3)) count++; - assert(count == 12); -} - -unittest -{ - // Test with non-default sorting order - auto a = [ 3, 2, 1 ]; - - assert(nextEvenPermutation!"a > b"(a) == true); - assert(a == [ 2, 1, 3 ]); - assert(nextEvenPermutation!"a > b"(a) == true); - assert(a == [ 1, 3, 2 ]); - assert(nextEvenPermutation!"a > b"(a) == false); - assert(a == [ 3, 2, 1 ]); -} - -unittest -{ - // Test various cases of rollover - auto a = [ 3, 1, 2 ]; - assert(nextEvenPermutation(a) == false); - assert(a == [ 1, 2, 3 ]); - - auto b = [ 3, 2, 1 ]; - assert(nextEvenPermutation(b) == false); - assert(b == [ 1, 3, 2 ]); -} - -/** -Even permutations are useful for generating coordinates of certain geometric -shapes. Here's a non-trivial example: -*/ -unittest -{ - import std.math : sqrt; - - // Print the 60 vertices of a uniform truncated icosahedron (soccer ball) - enum real Phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio - real[][] seeds = [ - [0.0, 1.0, 3.0*Phi], - [1.0, 2.0+Phi, 2.0*Phi], - [Phi, 2.0, Phi^^3] - ]; - size_t n; - foreach (seed; seeds) - { - // Loop over even permutations of each seed - do - { - // Loop over all sign changes of each permutation - size_t i; - do - { - // Generate all possible sign changes - for (i=0; i < seed.length; i++) - { - if (seed[i] != 0.0) - { - seed[i] = -seed[i]; - if (seed[i] < 0.0) - break; - } - } - n++; - } while (i < seed.length); - } while (nextEvenPermutation(seed)); - } - assert(n == 60); -} - -// cartesianProduct -/** -Lazily computes the Cartesian product of two or more ranges. The product is a -_range of tuples of elements from each respective range. - -The conditions for the two-range case are as follows: - -If both ranges are finite, then one must be (at least) a forward range and the -other an input range. - -If one _range is infinite and the other finite, then the finite _range must -be a forward _range, and the infinite range can be an input _range. - -If both ranges are infinite, then both must be forward ranges. - -When there are more than two ranges, the above conditions apply to each -adjacent pair of ranges. -*/ -auto cartesianProduct(R1, R2)(R1 range1, R2 range2) -{ - static if (isInfinite!R1 && isInfinite!R2) - { - static if (isForwardRange!R1 && isForwardRange!R2) - { - // This algorithm traverses the cartesian product by alternately - // covering the right and bottom edges of an increasing square area - // over the infinite table of combinations. This schedule allows us - // to require only forward ranges. - return zip(sequence!"n"(cast(size_t)0), range1.save, range2.save, - repeat(range1), repeat(range2)) - .map!(function(a) => chain( - zip(repeat(a[1]), take(a[4].save, a[0])), - zip(take(a[3].save, a[0]+1), repeat(a[2])) - ))() - .joiner(); - } - else static assert(0, "cartesianProduct of infinite ranges requires "~ - "forward ranges"); - } - else static if (isInputRange!R1 && isForwardRange!R2 && !isInfinite!R2) - { - return joiner(map!((ElementType!R1 a) => zip(repeat(a), range2.save)) - (range1)); - } - else static if (isInputRange!R2 && isForwardRange!R1 && !isInfinite!R1) - { - return joiner(map!((ElementType!R2 a) => zip(range1.save, repeat(a))) - (range2)); - } - else static assert(0, "cartesianProduct involving finite ranges must "~ - "have at least one finite forward range"); -} - -/// -unittest -{ - auto N = sequence!"n"(0); // the range of natural numbers - auto N2 = cartesianProduct(N, N); // the range of all pairs of natural numbers - - // Various arbitrary number pairs can be found in the range in finite time. - assert(canFind(N2, tuple(0, 0))); - assert(canFind(N2, tuple(123, 321))); - assert(canFind(N2, tuple(11, 35))); - assert(canFind(N2, tuple(279, 172))); -} - -/// -unittest -{ - auto B = [ 1, 2, 3 ]; - auto C = [ 4, 5, 6 ]; - auto BC = cartesianProduct(B, C); - - foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], - [2, 6], [3, 6]]) - { - assert(canFind(BC, tuple(n[0], n[1]))); - } -} - -unittest -{ - // Test cartesian product of two infinite ranges - auto Even = sequence!"2*n"(0); - auto Odd = sequence!"2*n+1"(0); - auto EvenOdd = cartesianProduct(Even, Odd); - - foreach (pair; [[0, 1], [2, 1], [0, 3], [2, 3], [4, 1], [4, 3], [0, 5], - [2, 5], [4, 5], [6, 1], [6, 3], [6, 5]]) - { - assert(canFind(EvenOdd, tuple(pair[0], pair[1]))); - } - - // This should terminate in finite time - assert(canFind(EvenOdd, tuple(124, 73))); - assert(canFind(EvenOdd, tuple(0, 97))); - assert(canFind(EvenOdd, tuple(42, 1))); -} - -unittest -{ - // Test cartesian product of an infinite input range and a finite forward - // range. - auto N = sequence!"n"(0); - auto M = [100, 200, 300]; - auto NM = cartesianProduct(N,M); - - foreach (pair; [[0, 100], [0, 200], [0, 300], [1, 100], [1, 200], [1, 300], - [2, 100], [2, 200], [2, 300], [3, 100], [3, 200], - [3, 300]]) - { - assert(canFind(NM, tuple(pair[0], pair[1]))); - } - - // We can't solve the halting problem, so we can only check a finite - // initial segment here. - assert(!canFind(NM.take(100), tuple(100, 0))); - assert(!canFind(NM.take(100), tuple(1, 1))); - assert(!canFind(NM.take(100), tuple(100, 200))); - - auto MN = cartesianProduct(M,N); - foreach (pair; [[100, 0], [200, 0], [300, 0], [100, 1], [200, 1], [300, 1], - [100, 2], [200, 2], [300, 2], [100, 3], [200, 3], - [300, 3]]) - { - assert(canFind(MN, tuple(pair[0], pair[1]))); - } - - // We can't solve the halting problem, so we can only check a finite - // initial segment here. - assert(!canFind(MN.take(100), tuple(0, 100))); - assert(!canFind(MN.take(100), tuple(0, 1))); - assert(!canFind(MN.take(100), tuple(100, 200))); -} - -unittest -{ - // Test cartesian product of two finite ranges. - auto X = [1, 2, 3]; - auto Y = [4, 5, 6]; - auto XY = cartesianProduct(X, Y); - auto Expected = [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4], - [3, 5], [3, 6]]; - - // Verify Expected ⊆ XY - foreach (pair; Expected) - { - assert(canFind(XY, tuple(pair[0], pair[1]))); - } - - // Verify XY ⊆ Expected - foreach (pair; XY) - { - assert(canFind(Expected, [pair[0], pair[1]])); - } - - // And therefore, by set comprehension, XY == Expected -} - -unittest -{ - auto N = sequence!"n"(0); - - // To force the template to fall to the second case, we wrap N in a struct - // that doesn't allow bidirectional access. - struct FwdRangeWrapper(R) - { - R impl; - - // Input range API - @property auto front() { return impl.front; } - void popFront() { impl.popFront(); } - static if (isInfinite!R) - enum empty = false; - else - @property bool empty() { return impl.empty; } - - // Forward range API - @property auto save() { return typeof(this)(impl.save); } - } - auto fwdWrap(R)(R range) { return FwdRangeWrapper!R(range); } - - // General test: two infinite bidirectional ranges - auto N2 = cartesianProduct(N, N); - - assert(canFind(N2, tuple(0, 0))); - assert(canFind(N2, tuple(123, 321))); - assert(canFind(N2, tuple(11, 35))); - assert(canFind(N2, tuple(279, 172))); - - // Test first case: forward range with bidirectional range - auto fwdN = fwdWrap(N); - auto N2_a = cartesianProduct(fwdN, N); - - assert(canFind(N2_a, tuple(0, 0))); - assert(canFind(N2_a, tuple(123, 321))); - assert(canFind(N2_a, tuple(11, 35))); - assert(canFind(N2_a, tuple(279, 172))); - - // Test second case: bidirectional range with forward range - auto N2_b = cartesianProduct(N, fwdN); - - assert(canFind(N2_b, tuple(0, 0))); - assert(canFind(N2_b, tuple(123, 321))); - assert(canFind(N2_b, tuple(11, 35))); - assert(canFind(N2_b, tuple(279, 172))); - - // Test third case: finite forward range with (infinite) input range - static struct InpRangeWrapper(R) - { - R impl; - - // Input range API - @property auto front() { return impl.front; } - void popFront() { impl.popFront(); } - static if (isInfinite!R) - enum empty = false; - else - @property bool empty() { return impl.empty; } - } - auto inpWrap(R)(R r) { return InpRangeWrapper!R(r); } - - auto inpN = inpWrap(N); - auto B = [ 1, 2, 3 ]; - auto fwdB = fwdWrap(B); - auto BN = cartesianProduct(fwdB, inpN); - - assert(equal(map!"[a[0],a[1]]"(BN.take(10)), [[1, 0], [2, 0], [3, 0], - [1, 1], [2, 1], [3, 1], [1, 2], [2, 2], [3, 2], [1, 3]])); - - // Test fourth case: (infinite) input range with finite forward range - auto NB = cartesianProduct(inpN, fwdB); - - assert(equal(map!"[a[0],a[1]]"(NB.take(10)), [[0, 1], [0, 2], [0, 3], - [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1]])); - - // General finite range case - auto C = [ 4, 5, 6 ]; - auto BC = cartesianProduct(B, C); - - foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], - [2, 6], [3, 6]]) - { - assert(canFind(BC, tuple(n[0], n[1]))); - } -} - -/// ditto -auto cartesianProduct(RR...)(RR ranges) - if (ranges.length > 2 && - allSatisfy!(isForwardRange, RR) && - !anySatisfy!(isInfinite, RR)) -{ - // This overload uses a much less template-heavy implementation when - // all ranges are finite forward ranges, which is the most common use - // case, so that we don't run out of resources too quickly. - // - // For infinite ranges or non-forward ranges, we fall back to the old - // implementation which expands an exponential number of templates. - import std.typecons : tuple; - - static struct Result - { - RR ranges; - RR current; - bool empty = true; - - this(RR _ranges) - { - ranges = _ranges; - empty = false; - foreach (i, r; ranges) - { - current[i] = r.save; - if (current[i].empty) - empty = true; - } - } - @property auto front() - { - return mixin(algoFormat("tuple(%(current[%d].front%|,%))", - iota(0, current.length))); - } - void popFront() - { - foreach_reverse (i, ref r; current) - { - r.popFront(); - if (!r.empty) break; - - static if (i==0) - empty = true; - else - r = ranges[i].save; // rollover - } - } - @property Result save() - { - Result copy; - foreach (i, r; ranges) - { - copy.ranges[i] = r.save; - copy.current[i] = current[i].save; - } - copy.empty = this.empty; - return copy; - } - } - static assert(isForwardRange!Result); - - return Result(ranges); -} - -unittest -{ - // Issue 10693: cartesian product of empty ranges should be empty. - int[] a, b, c, d, e; - auto cprod = cartesianProduct(a,b,c,d,e); - assert(cprod.empty); - foreach (_; cprod) {} // should not crash - - // Test case where only one of the ranges is empty: the result should still - // be empty. - int[] p=[1], q=[]; - auto cprod2 = cartesianProduct(p,p,p,q,p); - assert(cprod2.empty); - foreach (_; cprod2) {} // should not crash -} - -unittest -{ - // .init value of cartesianProduct should be empty - auto cprod = cartesianProduct([0,0], [1,1], [2,2]); - assert(!cprod.empty); - assert(cprod.init.empty); -} - -unittest -{ - // Issue 13393 - assert(!cartesianProduct([0],[0],[0]).save.empty); -} - -/// ditto -auto cartesianProduct(R1, R2, RR...)(R1 range1, R2 range2, RR otherRanges) - if (!allSatisfy!(isForwardRange, R1, R2, RR) || - anySatisfy!(isInfinite, R1, R2, RR)) -{ - /* We implement the n-ary cartesian product by recursively invoking the - * binary cartesian product. To make the resulting range nicer, we denest - * one level of tuples so that a ternary cartesian product, for example, - * returns 3-element tuples instead of nested 2-element tuples. - */ - enum string denest = algoFormat("tuple(a[0], %(a[1][%d]%|,%))", - iota(0, otherRanges.length+1)); - return map!denest( - cartesianProduct(range1, cartesianProduct(range2, otherRanges)) - ); -} - -unittest -{ - auto N = sequence!"n"(0); - auto N3 = cartesianProduct(N, N, N); - - // Check that tuples are properly denested - assert(is(ElementType!(typeof(N3)) == Tuple!(size_t,size_t,size_t))); - - assert(canFind(N3, tuple(0, 27, 7))); - assert(canFind(N3, tuple(50, 23, 71))); - assert(canFind(N3, tuple(9, 3, 0))); -} - -version(none) -// This unittest causes `make -f posix.mak unittest` to run out of memory. Why? -unittest -{ - auto N = sequence!"n"(0); - auto N4 = cartesianProduct(N, N, N, N); - - // Check that tuples are properly denested - assert(is(ElementType!(typeof(N4)) == Tuple!(size_t,size_t,size_t,size_t))); - - assert(canFind(N4, tuple(1, 2, 3, 4))); - assert(canFind(N4, tuple(4, 3, 2, 1))); - assert(canFind(N4, tuple(10, 31, 7, 12))); -} - -/// Issue 9878 -unittest -{ - auto A = [ 1, 2, 3 ]; - auto B = [ 'a', 'b', 'c' ]; - auto C = [ "x", "y", "z" ]; - auto ABC = cartesianProduct(A, B, C); - - assert(ABC.equal([ - tuple(1, 'a', "x"), tuple(1, 'a', "y"), tuple(1, 'a', "z"), - tuple(1, 'b', "x"), tuple(1, 'b', "y"), tuple(1, 'b', "z"), - tuple(1, 'c', "x"), tuple(1, 'c', "y"), tuple(1, 'c', "z"), - tuple(2, 'a', "x"), tuple(2, 'a', "y"), tuple(2, 'a', "z"), - tuple(2, 'b', "x"), tuple(2, 'b', "y"), tuple(2, 'b', "z"), - tuple(2, 'c', "x"), tuple(2, 'c', "y"), tuple(2, 'c', "z"), - tuple(3, 'a', "x"), tuple(3, 'a', "y"), tuple(3, 'a', "z"), - tuple(3, 'b', "x"), tuple(3, 'b', "y"), tuple(3, 'b', "z"), - tuple(3, 'c', "x"), tuple(3, 'c', "y"), tuple(3, 'c', "z") - ])); -} - -pure @safe nothrow @nogc unittest -{ - int[2] A = [1,2]; - auto C = cartesianProduct(A[], A[], A[]); - assert(isForwardRange!(typeof(C))); - - C.popFront(); - auto front1 = C.front; - auto D = C.save; - C.popFront(); - assert(D.front == front1); -} - -/** -Find $(D value) _among $(D values), returning the 1-based index -of the first matching value in $(D values), or $(D 0) if $(D value) -is not _among $(D values). The predicate $(D pred) is used to -compare values, and uses equality by default. - -See_Also: -$(XREF algorithm, find) for finding a value in a range. -*/ -uint among(alias pred = (a, b) => a == b, Value, Values...) - (Value value, Values values) - if (Values.length != 0) -{ - foreach (uint i, ref v; values) - { - import std.functional : binaryFun; - if (binaryFun!pred(value, v)) return i + 1; - } - return 0; -} - -/// Ditto -template among(values...) - if (isExpressionTuple!values) -{ - uint among(Value)(Value value) - if (!is(CommonType!(Value, values) == void)) - { - switch (value) - { - foreach (uint i, v; values) - case v: - return i + 1; - default: - return 0; - } - } -} - -/// -unittest -{ - assert(3.among(1, 42, 24, 3, 2)); - - if (auto pos = "bar".among("foo", "bar", "baz")) - assert(pos == 2); - else - assert(false); - - // 42 is larger than 24 - assert(42.among!((lhs, rhs) => lhs > rhs)(43, 24, 100) == 2); -} - -/** -Alternatively, $(D values) can be passed at compile-time, allowing for a more -efficient search, but one that only supports matching on equality: -*/ -unittest -{ - assert(3.among!(2, 3, 4)); - assert("bar".among!("foo", "bar", "baz") == 2); -} - -unittest -{ - if (auto pos = 3.among(1, 2, 3)) - assert(pos == 3); - else - assert(false); - assert(!4.among(1, 2, 3)); - - auto position = "hello".among("hello", "world"); - assert(position); - assert(position == 1); - - alias values = TypeTuple!("foo", "bar", "baz"); - auto arr = [values]; - assert(arr[0 .. "foo".among(values)] == ["foo"]); - assert(arr[0 .. "bar".among(values)] == ["foo", "bar"]); - assert(arr[0 .. "baz".among(values)] == arr); - assert("foobar".among(values) == 0); - - if (auto pos = 3.among!(1, 2, 3)) - assert(pos == 3); - else - assert(false); - assert(!4.among!(1, 2, 3)); - - position = "hello".among!("hello", "world"); - assert(position); - assert(position == 1); - - static assert(!__traits(compiles, "a".among!("a", 42))); - static assert(!__traits(compiles, (Object.init).among!(42, "a"))); -} - -/** -Returns one of a collection of expressions based on the value of the switch -expression. - -$(D choices) needs to be composed of pairs of test expressions and return -expressions. Each test-expression is compared with $(D switchExpression) using -$(D pred)($(D switchExpression) is the first argument) and if that yields true -- the return expression is returned. - -Both the test and the return expressions are lazily evaluated. - -Params: - -switchExpression = The first argument for the predicate. - -choices = Pairs of test expressions and return expressions. The test -expressions will be the second argument for the predicate, and the return -expression will be returned if the predicate yields true with $(D -switchExpression) and the test expression as arguments. May also have a -default return expression, that needs to be the last expression without a test -expression before it. A return expression may be of void type only if it -always throws. - -Returns: The return expression associated with the first test expression that -made the predicate yield true, or the default return expression if no test -expression matched. - -Throws: If there is no default return expression and the predicate does not -yield true with any test expression - $(D SwitchError) is thrown. $(D -SwitchError) is also thrown if a void return expression was executed without -throwing anything. -*/ -auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choices) -{ - import core.exception : SwitchError; - alias predicate = binaryFun!(pred); - - foreach (index, ChoiceType; R) - { - //The even places in `choices` are for the predicate. - static if (index % 2 == 1) - { - if (predicate(switchExpression, choices[index - 1]())) - { - static if (is(typeof(choices[index]()) == void)) - { - choices[index](); - throw new SwitchError("Choices that return void should throw"); - } - else - { - return choices[index](); - } - } - } - } - - //In case nothing matched: - static if (R.length % 2 == 1) //If there is a default return expression: - { - static if (is(typeof(choices[$ - 1]()) == void)) - { - choices[$ - 1](); - throw new SwitchError("Choices that return void should throw"); - } - else - { - return choices[$ - 1](); - } - } - else //If there is no default return expression: - { - throw new SwitchError("Input not matched by any pattern"); - } -} - -/// -unittest -{ - string res = 2.predSwitch!"a < b"( - 1, "less than 1", - 5, "less than 5", - 10, "less than 10", - "greater or equal to 10"); - - assert(res == "less than 5"); - - //The arguments are lazy, which allows us to use predSwitch to create - //recursive functions: - int factorial(int n) - { - return n.predSwitch!"a <= b"( - -1, {throw new Exception("Can not calculate n! for n < 0");}(), - 0, 1, // 0! = 1 - n * factorial(n - 1) // n! = n * (n - 1)! for n >= 0 - ); - } - assert(factorial(3) == 6); - - //Void return expressions are allowed if they always throw: - import std.exception : assertThrown; - assertThrown!Exception(factorial(-9)); -} - -unittest -{ - import core.exception : SwitchError; - import std.exception : assertThrown; - - //Nothing matches - with default return expression: - assert(20.predSwitch!"a < b"( - 1, "less than 1", - 5, "less than 5", - 10, "less than 10", - "greater or equal to 10") == "greater or equal to 10"); - - //Nothing matches - without default return expression: - assertThrown!SwitchError(20.predSwitch!"a < b"( - 1, "less than 1", - 5, "less than 5", - 10, "less than 10", - )); - - //Using the default predicate: - assert(2.predSwitch( - 1, "one", - 2, "two", - 3, "three", - ) == "two"); - - //Void return expressions must always throw: - assertThrown!SwitchError(1.predSwitch( - 0, "zero", - 1, {}(), //A void return expression that doesn't throw - 2, "two", - )); -} diff --git a/std/algorithm/comparison.d b/std/algorithm/comparison.d new file mode 100644 index 00000000000..a57f30de748 --- /dev/null +++ b/std/algorithm/comparison.d @@ -0,0 +1,1534 @@ +// Written in the D programming language. +/** +This is a submodule of $(LINK2 std_algorithm_package.html, std.algorithm). +It contains generic _comparison algorithms. + +$(BOOKTABLE Cheat Sheet, + +$(TR $(TH Function Name) $(TH Description)) + +$(T2 among, + Checks if a value is among a set of values, e.g. + $(D if (v.among(1, 2, 3)) // `v` is 1, 2 or 3)) +$(T2 castSwitch, + $(D (new A()).castSwitch((A a)=>1,(B b)=>2)) returns $(D 1).) +$(T2 clamp, + $(D clamp(1, 3, 6)) returns $(D 3). $(D clamp(4, 3, 6)) returns $(D 4).) +$(T2 cmp, + $(D cmp("abc", "abcd")) is $(D -1), $(D cmp("abc", "aba")) is $(D 1), + and $(D cmp("abc", "abc")) is $(D 0).) +$(T2 equal, + Compares ranges for element-by-element equality, e.g. + $(D equal([1, 2, 3], [1.0, 2.0, 3.0])) returns $(D true).) +$(T2 levenshteinDistance, + $(D levenshteinDistance("kitten", "sitting")) returns $(D 3) by using + the $(LUCKY Levenshtein distance _algorithm).) +$(T2 levenshteinDistanceAndPath, + $(D levenshteinDistanceAndPath("kitten", "sitting")) returns + $(D tuple(3, "snnnsni")) by using the $(LUCKY Levenshtein distance + _algorithm).) +$(T2 max, + $(D max(3, 4, 2)) returns $(D 4).) +$(T2 min, + $(D min(3, 4, 2)) returns $(D 2).) +$(T2 mismatch, + $(D mismatch("oh hi", "ohayo")) returns $(D tuple(" hi", "ayo")).) +$(T2 predSwitch, + $(D 2.predSwitch(1, "one", 2, "two", 3, "three")) returns $(D "two").) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_comparison.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.comparison; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +import std.traits; +// FIXME +import std.typecons; // : tuple, Tuple; + +/** +Find $(D value) _among $(D values), returning the 1-based index +of the first matching value in $(D values), or $(D 0) if $(D value) +is not _among $(D values). The predicate $(D pred) is used to +compare values, and uses equality by default. + +See_Also: +$(LREF find) and $(LREF canFind) for finding a value in a +range. +*/ +uint among(alias pred = (a, b) => a == b, Value, Values...) + (Value value, Values values) + if (Values.length != 0) +{ + foreach (uint i, ref v; values) + { + import std.functional : binaryFun; + if (binaryFun!pred(value, v)) return i + 1; + } + return 0; +} + +/// Ditto +template among(values...) + if (isExpressionTuple!values) +{ + uint among(Value)(Value value) + if (!is(CommonType!(Value, values) == void)) + { + switch (value) + { + foreach (uint i, v; values) + case v: + return i + 1; + default: + return 0; + } + } +} + +/// +@safe unittest +{ + assert(3.among(1, 42, 24, 3, 2)); + + if (auto pos = "bar".among("foo", "bar", "baz")) + assert(pos == 2); + else + assert(false); + + // 42 is larger than 24 + assert(42.among!((lhs, rhs) => lhs > rhs)(43, 24, 100) == 2); +} + +/** +Alternatively, $(D values) can be passed at compile-time, allowing for a more +efficient search, but one that only supports matching on equality: +*/ +@safe unittest +{ + assert(3.among!(2, 3, 4)); + assert("bar".among!("foo", "bar", "baz") == 2); +} + +@safe unittest +{ + import std.typetuple : TypeTuple; + + if (auto pos = 3.among(1, 2, 3)) + assert(pos == 3); + else + assert(false); + assert(!4.among(1, 2, 3)); + + auto position = "hello".among("hello", "world"); + assert(position); + assert(position == 1); + + alias values = TypeTuple!("foo", "bar", "baz"); + auto arr = [values]; + assert(arr[0 .. "foo".among(values)] == ["foo"]); + assert(arr[0 .. "bar".among(values)] == ["foo", "bar"]); + assert(arr[0 .. "baz".among(values)] == arr); + assert("foobar".among(values) == 0); + + if (auto pos = 3.among!(1, 2, 3)) + assert(pos == 3); + else + assert(false); + assert(!4.among!(1, 2, 3)); + + position = "hello".among!("hello", "world"); + assert(position); + assert(position == 1); + + static assert(!__traits(compiles, "a".among!("a", 42))); + static assert(!__traits(compiles, (Object.init).among!(42, "a"))); +} + +// Used in castSwitch to find the first choice that overshadows the last choice +// in a tuple. +private template indexOfFirstOvershadowingChoiceOnLast(choices...) +{ + alias firstParameterTypes = ParameterTypeTuple!(choices[0]); + alias lastParameterTypes = ParameterTypeTuple!(choices[$ - 1]); + + static if (lastParameterTypes.length == 0) + { + // If the last is null-typed choice, check if the first is null-typed. + enum isOvershadowing = firstParameterTypes.length == 0; + } + else static if (firstParameterTypes.length == 1) + { + // If the both first and last are not null-typed, check for overshadowing. + enum isOvershadowing = + is(firstParameterTypes[0] == Object) // Object overshadows all other classes!(this is needed for interfaces) + || is(lastParameterTypes[0] : firstParameterTypes[0]); + } + else + { + // If the first is null typed and the last is not - the is no overshadowing. + enum isOvershadowing = false; + } + + static if (isOvershadowing) + { + enum indexOfFirstOvershadowingChoiceOnLast = 0; + } + else + { + enum indexOfFirstOvershadowingChoiceOnLast = + 1 + indexOfFirstOvershadowingChoiceOnLast!(choices[1..$]); + } +} + +/** +Executes and returns one of a collection of handlers based on the type of the +switch object. + +$(D choices) needs to be composed of function or delegate handlers that accept +one argument. The first choice that $(D switchObject) can be casted to the type +of argument it accepts will be called with $(D switchObject) casted to that +type, and the value it'll return will be returned by $(D castSwitch). + +There can also be a choice that accepts zero arguments. That choice will be +invoked if $(D switchObject) is null. + +If a choice's return type is void, the choice must throw an exception, unless +all the choices are void. In that case, castSwitch itself will return void. + +Throws: If none of the choice matches, a $(D SwitchError) will be thrown. $(D +SwitchError) will also be thrown if not all the choices are void and a void +choice was executed without throwing anything. + +Note: $(D castSwitch) can only be used with object types. +*/ +auto castSwitch(choices...)(Object switchObject) +{ + import core.exception : SwitchError; + + // Check to see if all handlers return void. + enum areAllHandlersVoidResult={ + foreach(index, choice; choices) + { + if(!is(ReturnType!choice == void)) + { + return false; + } + } + return true; + }(); + + if (switchObject !is null) + { + + // Checking for exact matches: + ClassInfo classInfo = typeid(switchObject); + foreach (index, choice; choices) + { + static assert(isCallable!choice, + "A choice handler must be callable"); + + alias choiceParameterTypes = ParameterTypeTuple!choice; + static assert(choiceParameterTypes.length <= 1, + "A choice handler can not have more than one argument."); + + static if (choiceParameterTypes.length == 1) + { + alias CastClass = choiceParameterTypes[0]; + static assert(is(CastClass == class) || is(CastClass == interface), + "A choice handler can have only class or interface typed argument."); + + // Check for overshadowing: + immutable indexOfOvershadowingChoice = + indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]); + static assert(indexOfOvershadowingChoice == index, + "choice number %d(type %s) is overshadowed by choice number %d(type %s)".format( + index + 1, CastClass.stringof, indexOfOvershadowingChoice + 1, + ParameterTypeTuple!(choices[indexOfOvershadowingChoice])[0].stringof)); + + if (classInfo == typeid(CastClass)) + { + static if(is(ReturnType!(choice) == void)) + { + choice(cast(CastClass) switchObject); + static if (areAllHandlersVoidResult) + { + return; + } + else + { + throw new SwitchError("Handlers that return void should throw"); + } + } + else + { + return choice(cast(CastClass) switchObject); + } + } + } + } + + // Checking for derived matches: + foreach (choice; choices) + { + alias choiceParameterTypes = ParameterTypeTuple!choice; + static if (choiceParameterTypes.length == 1) + { + if (auto castedObject = cast(choiceParameterTypes[0]) switchObject) + { + static if(is(ReturnType!(choice) == void)) + { + choice(castedObject); + static if (areAllHandlersVoidResult) + { + return; + } + else + { + throw new SwitchError("Handlers that return void should throw"); + } + } + else + { + return choice(castedObject); + } + } + } + } + } + else // If switchObject is null: + { + // Checking for null matches: + foreach (index, choice; choices) + { + static if (ParameterTypeTuple!(choice).length == 0) + { + immutable indexOfOvershadowingChoice = + indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]); + + // Check for overshadowing: + static assert(indexOfOvershadowingChoice == index, + "choice number %d(null reference) is overshadowed by choice number %d(null reference)".format( + index + 1, indexOfOvershadowingChoice + 1)); + + if (switchObject is null) + { + static if(is(ReturnType!(choice) == void)) + { + choice(); + static if (areAllHandlersVoidResult) + { + return; + } + else + { + throw new SwitchError("Handlers that return void should throw"); + } + } + else + { + return choice(); + } + } + } + } + } + + // In case nothing matched: + throw new SwitchError("Input not matched by any choice"); +} + +/// +unittest +{ + import std.algorithm.iteration : map; + import std.format : format; + + class A + { + int a; + this(int a) {this.a = a;} + @property int i() { return a; } + } + interface I { } + class B : I { } + + Object[] arr = [new A(1), new B(), null]; + + auto results = arr.map!(castSwitch!( + (A a) => "A with a value of %d".format(a.a), + (I i) => "derived from I", + () => "null reference", + ))(); + + // A is handled directly: + assert(results[0] == "A with a value of 1"); + // B has no handler - it is handled by the handler of I: + assert(results[1] == "derived from I"); + // null is handled by the null handler: + assert(results[2] == "null reference"); +} + +/// Using with void handlers: +unittest +{ + import std.exception : assertThrown; + + class A { } + class B { } + // Void handlers are allowed if they throw: + assertThrown!Exception( + new B().castSwitch!( + (A a) => 1, + (B d) { throw new Exception("B is not allowed!"); } + )() + ); + + // Void handlers are also allowed if all the handlers are void: + new A().castSwitch!( + (A a) { assert(true); }, + (B b) { assert(false); }, + )(); +} + +unittest +{ + import core.exception : SwitchError; + import std.exception : assertThrown; + + interface I { } + class A : I { } + class B { } + + // Nothing matches: + assertThrown!SwitchError((new A()).castSwitch!( + (B b) => 1, + () => 2, + )()); + + // Choices with multiple arguments are not allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (A a, B b) => 0, + )())); + + // Only callable handlers allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + 1234, + )())); + + // Only object arguments allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (int x) => 0, + )())); + + // Object overshadows regular classes: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (Object o) => 0, + (A a) => 1, + )())); + + // Object overshadows interfaces: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (Object o) => 0, + (I i) => 1, + )())); + + // No multiple null handlers allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + () => 0, + () => 1, + )())); + + // No non-throwing void handlers allowed(when there are non-void handlers): + assertThrown!SwitchError((new A()).castSwitch!( + (A a) {}, + (B b) => 2, + )()); + + // All-void handlers work for the null case: + null.castSwitch!( + (Object o) { assert(false); }, + () { assert(true); }, + )(); + + // Throwing void handlers work for the null case: + assertThrown!Exception(null.castSwitch!( + (Object o) => 1, + () { throw new Exception("null"); }, + )()); +} + +/** +Returns $(D val), if it is between $(D lower) and $(D upper). +Otherwise returns the nearest of the two. Equivalent to $(D max(lower, +min(upper,val))). +*/ +auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) +in +{ + import std.functional : greaterThan; + assert(!lower.greaterThan(upper)); +} +body +{ + return max(lower, min(upper, val)); +} + +/// +@safe unittest +{ + assert(clamp(2, 1, 3) == 2); + assert(clamp(0, 1, 3) == 1); + assert(clamp(4, 1, 3) == 3); + + assert(clamp(1, 1, 1) == 1); + + assert(clamp(5, -1, 2u) == 2); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int a = 1; + short b = 6; + double c = 2; + static assert(is(typeof(clamp(c,a,b)) == double)); + assert(clamp(c, a, b) == c); + assert(clamp(a-c, a, b) == a); + assert(clamp(b+c, a, b) == b); + // mixed sign + a = -5; + uint f = 5; + static assert(is(typeof(clamp(f, a, b)) == int)); + assert(clamp(f, a, b) == f); + // similar type deduction for (u)long + static assert(is(typeof(clamp(-1L, -2L, 2UL)) == long)); + + // user-defined types + import std.datetime; + assert(clamp(Date(1982, 1, 4), Date(1012, 12, 21), Date(2012, 12, 21)) == Date(1982, 1, 4)); + assert(clamp(Date(1982, 1, 4), Date.min, Date.max) == Date(1982, 1, 4)); + // UFCS style + assert(Date(1982, 1, 4).clamp(Date.min, Date.max) == Date(1982, 1, 4)); + +} + +// cmp +/********************************** +Performs three-way lexicographical comparison on two input ranges +according to predicate $(D pred). Iterating $(D r1) and $(D r2) in +lockstep, $(D cmp) compares each element $(D e1) of $(D r1) with the +corresponding element $(D e2) in $(D r2). If $(D binaryFun!pred(e1, +e2)), $(D cmp) returns a negative value. If $(D binaryFun!pred(e2, +e1)), $(D cmp) returns a positive value. If one of the ranges has been +finished, $(D cmp) returns a negative value if $(D r1) has fewer +elements than $(D r2), a positive value if $(D r1) has more elements +than $(D r2), and $(D 0) if the ranges have the same number of +elements. + +If the ranges are strings, $(D cmp) performs UTF decoding +appropriately and compares the ranges one code point at a time. +*/ +int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2 && !(isSomeString!R1 && isSomeString!R2)) +{ + for (;; r1.popFront(), r2.popFront()) + { + if (r1.empty) return -cast(int)!r2.empty; + if (r2.empty) return !r1.empty; + auto a = r1.front, b = r2.front; + if (binaryFun!pred(a, b)) return -1; + if (binaryFun!pred(b, a)) return 1; + } +} + +// Specialization for strings (for speed purposes) +int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) if (isSomeString!R1 && isSomeString!R2) +{ + import core.stdc.string : memcmp; + import std.utf : decode; + + static if (is(typeof(pred) : string)) + enum isLessThan = pred == "a < b"; + else + enum isLessThan = false; + + // For speed only + static int threeWay(size_t a, size_t b) + { + static if (size_t.sizeof == int.sizeof && isLessThan) + return a - b; + else + return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0; + } + // For speed only + // @@@BUG@@@ overloading should be allowed for nested functions + static int threeWayInt(int a, int b) + { + static if (isLessThan) + return a - b; + else + return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0; + } + + static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof && isLessThan) + { + static if (typeof(r1[0]).sizeof == 1) + { + immutable len = min(r1.length, r2.length); + immutable result = __ctfe ? + { + foreach (i; 0 .. len) + { + if (r1[i] != r2[i]) + return threeWayInt(r1[i], r2[i]); + } + return 0; + }() + : () @trusted { return memcmp(r1.ptr, r2.ptr, len); }(); + if (result) return result; + } + else + { + auto p1 = r1.ptr, p2 = r2.ptr, + pEnd = p1 + min(r1.length, r2.length); + for (; p1 != pEnd; ++p1, ++p2) + { + if (*p1 != *p2) return threeWayInt(cast(int) *p1, cast(int) *p2); + } + } + return threeWay(r1.length, r2.length); + } + else + { + for (size_t i1, i2;;) + { + if (i1 == r1.length) return threeWay(i2, r2.length); + if (i2 == r2.length) return threeWay(r1.length, i1); + immutable c1 = std.utf.decode(r1, i1), + c2 = std.utf.decode(r2, i2); + if (c1 != c2) return threeWayInt(cast(int) c1, cast(int) c2); + } + } +} + +/// +@safe unittest +{ + int result; + + debug(string) printf("string.cmp.unittest\n"); + result = cmp("abc", "abc"); + assert(result == 0); + // result = cmp(null, null); + // assert(result == 0); + result = cmp("", ""); + assert(result == 0); + result = cmp("abc", "abcd"); + assert(result < 0); + result = cmp("abcd", "abc"); + assert(result > 0); + result = cmp("abc"d, "abd"); + assert(result < 0); + result = cmp("bbc", "abc"w); + assert(result > 0); + result = cmp("aaa", "aaaa"d); + assert(result < 0); + result = cmp("aaaa", "aaa"d); + assert(result > 0); + result = cmp("aaa", "aaa"d); + assert(result == 0); + result = cmp(cast(int[])[], cast(int[])[]); + assert(result == 0); + result = cmp([1, 2, 3], [1, 2, 3]); + assert(result == 0); + result = cmp([1, 3, 2], [1, 2, 3]); + assert(result > 0); + result = cmp([1, 2, 3], [1L, 2, 3, 4]); + assert(result < 0); + result = cmp([1L, 2, 3], [1, 2]); + assert(result > 0); +} + +// equal +/** +Compares two ranges for equality, as defined by predicate $(D pred) +(which is $(D ==) by default). +*/ +template equal(alias pred = "a == b") +{ + /++ + Returns $(D true) if and only if the two ranges compare equal element + for element, according to binary predicate $(D pred). The ranges may + have different element types, as long as $(D pred(a, b)) evaluates to + $(D bool) for $(D a) in $(D r1) and $(D b) in $(D r2). Performs + $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred). + + See_Also: + $(WEB sgi.com/tech/stl/_equal.html, STL's _equal) + +/ + bool equal(Range1, Range2)(Range1 r1, Range2 r2) + if (isInputRange!Range1 && isInputRange!Range2 && is(typeof(binaryFun!pred(r1.front, r2.front)))) + { + //Start by detecting default pred and compatible dynamicarray. + static if (is(typeof(pred) == string) && pred == "a == b" && + isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2))) + { + return r1 == r2; + } + //Try a fast implementation when the ranges have comparable lengths + else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length))) + { + auto len1 = r1.length; + auto len2 = r2.length; + if (len1 != len2) return false; //Short circuit return + + //Lengths are the same, so we need to do an actual comparison + //Good news is we can sqeeze out a bit of performance by not checking if r2 is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (!binaryFun!(pred)(r1.front, r2.front)) return false; + } + return true; + } + else + { + //Generic case, we have to walk both ranges making sure neither is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (r2.empty) return false; + if (!binaryFun!(pred)(r1.front, r2.front)) return false; + } + return r2.empty; + } + } +} + +/// +@safe unittest +{ + import std.math : approxEqual; + import std.algorithm : equal; + + int[] a = [ 1, 2, 4, 3 ]; + assert(!equal(a, a[1..$])); + assert(equal(a, a)); + + // different types + double[] b = [ 1.0, 2, 4, 3]; + assert(!equal(a, b[1..$])); + assert(equal(a, b)); + + // predicated: ensure that two vectors are approximately equal + double[] c = [ 1.005, 2, 4, 3]; + assert(equal!approxEqual(b, c)); +} + +/++ +Tip: $(D equal) can itself be used as a predicate to other functions. +This can be very useful when the element type of a range is itself a +range. In particular, $(D equal) can be its own predicate, allowing +range of range (of range...) comparisons. + +/ +@safe unittest +{ + import std.range : iota, chunks; + import std.algorithm : equal; + assert(equal!(equal!equal)( + [[[0, 1], [2, 3]], [[4, 5], [6, 7]]], + iota(0, 8).chunks(2).chunks(2) + )); +} + +@safe unittest +{ + import std.algorithm.iteration : map; + import std.math : approxEqual; + import std.internal.test.dummyrange; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + // various strings + assert(equal("æøå", "æøå")); //UTF8 vs UTF8 + assert(!equal("???", "æøå")); //UTF8 vs UTF8 + assert(equal("æøå"w, "æøå"d)); //UTF16 vs UTF32 + assert(!equal("???"w, "æøå"d));//UTF16 vs UTF32 + assert(equal("æøå"d, "æøå"d)); //UTF32 vs UTF32 + assert(!equal("???"d, "æøå"d));//UTF32 vs UTF32 + assert(!equal("hello", "world")); + + // same strings, but "explicit non default" comparison (to test the non optimized array comparison) + assert( equal!("a == b")("æøå", "æøå")); //UTF8 vs UTF8 + assert(!equal!("a == b")("???", "æøå")); //UTF8 vs UTF8 + assert( equal!("a == b")("æøå"w, "æøå"d)); //UTF16 vs UTF32 + assert(!equal!("a == b")("???"w, "æøå"d));//UTF16 vs UTF32 + assert( equal!("a == b")("æøå"d, "æøå"d)); //UTF32 vs UTF32 + assert(!equal!("a == b")("???"d, "æøå"d));//UTF32 vs UTF32 + assert(!equal!("a == b")("hello", "world")); + + //Array of string + assert(equal(["hello", "world"], ["hello", "world"])); + assert(!equal(["hello", "world"], ["hello"])); + assert(!equal(["hello", "world"], ["hello", "Bob!"])); + + //Should not compile, because "string == dstring" is illegal + static assert(!is(typeof(equal(["hello", "world"], ["hello"d, "world"d])))); + //However, arrays of non-matching string can be compared using equal!equal. Neat-o! + equal!equal(["hello", "world"], ["hello"d, "world"d]); + + //Tests, with more fancy map ranges + int[] a = [ 1, 2, 4, 3 ]; + assert(equal([2, 4, 8, 6], map!"a*2"(a))); + double[] b = [ 1.0, 2, 4, 3]; + double[] c = [ 1.005, 2, 4, 3]; + assert(equal!approxEqual(map!"a*2"(b), map!"a*2"(c))); + assert(!equal([2, 4, 1, 3], map!"a*2"(a))); + assert(!equal([2, 4, 1], map!"a*2"(a))); + assert(!equal!approxEqual(map!"a*3"(b), map!"a*2"(c))); + + //Tests with some fancy reference ranges. + ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]); + ReferenceForwardRange!int cfr = new ReferenceForwardRange!int([1, 2, 4, 3]); + assert(equal(cir, a)); + cir = new ReferenceInputRange!int([1, 2, 4, 3]); + assert(equal(cir, cfr.save)); + assert(equal(cfr.save, cfr.save)); + cir = new ReferenceInputRange!int([1, 2, 8, 1]); + assert(!equal(cir, cfr)); + + //Test with an infinte range + ReferenceInfiniteForwardRange!int ifr = new ReferenceInfiniteForwardRange!int; + assert(!equal(a, ifr)); +} + +// MaxType +private template MaxType(T...) + if (T.length >= 1) +{ + static if (T.length == 1) + { + alias MaxType = T[0]; + } + else static if (T.length == 2) + { + static if (!is(typeof(T[0].min))) + alias MaxType = CommonType!T; + else static if (T[1].max > T[0].max) + alias MaxType = T[1]; + else + alias MaxType = T[0]; + } + else + { + alias MaxType = MaxType!(MaxType!(T[0 .. ($+1)/2]), MaxType!(T[($+1)/2 .. $])); + } +} + +// levenshteinDistance +/** +Encodes $(WEB realityinteractive.com/rgrzywinski/archives/000249.html, +edit operations) necessary to transform one sequence into +another. Given sequences $(D s) (source) and $(D t) (target), a +sequence of $(D EditOp) encodes the steps that need to be taken to +convert $(D s) into $(D t). For example, if $(D s = "cat") and $(D +"cars"), the minimal sequence that transforms $(D s) into $(D t) is: +skip two characters, replace 't' with 'r', and insert an 's'. Working +with edit operations is useful in applications such as spell-checkers +(to find the closest word to a given misspelled word), approximate +searches, diff-style programs that compute the difference between +files, efficient encoding of patches, DNA sequence analysis, and +plagiarism detection. +*/ + +enum EditOp : char +{ + /** Current items are equal; no editing is necessary. */ + none = 'n', + /** Substitute current item in target with current item in source. */ + substitute = 's', + /** Insert current item from the source into the target. */ + insert = 'i', + /** Remove current item from the target. */ + remove = 'r' +} + +struct Levenshtein(Range, alias equals, CostType = size_t) +{ + void deletionIncrement(CostType n) + { + _deletionIncrement = n; + InitMatrix(); + } + + void insertionIncrement(CostType n) + { + _insertionIncrement = n; + InitMatrix(); + } + + CostType distance(Range s, Range t) + { + auto slen = walkLength(s.save), tlen = walkLength(t.save); + AllocMatrix(slen + 1, tlen + 1); + foreach (i; 1 .. rows) + { + auto sfront = s.front; + s.popFront(); + auto tt = t; + foreach (j; 1 .. cols) + { + auto cSub = matrix(i - 1,j - 1) + + (equals(sfront, tt.front) ? 0 : _substitutionIncrement); + tt.popFront(); + auto cIns = matrix(i,j - 1) + _insertionIncrement; + auto cDel = matrix(i - 1,j) + _deletionIncrement; + switch (min_index(cSub, cIns, cDel)) + { + case 0: + matrix(i,j) = cSub; + break; + case 1: + matrix(i,j) = cIns; + break; + default: + matrix(i,j) = cDel; + break; + } + } + } + return matrix(slen,tlen); + } + + EditOp[] path(Range s, Range t) + { + distanceWithPath(s, t); + return path(); + } + + EditOp[] path() + { + import std.algorithm.mutation : reverse; + + EditOp[] result; + size_t i = rows - 1, j = cols - 1; + // restore the path + while (i || j) { + auto cIns = j == 0 ? CostType.max : matrix(i,j - 1); + auto cDel = i == 0 ? CostType.max : matrix(i - 1,j); + auto cSub = i == 0 || j == 0 + ? CostType.max + : matrix(i - 1,j - 1); + switch (min_index(cSub, cIns, cDel)) { + case 0: + result ~= matrix(i - 1,j - 1) == matrix(i,j) + ? EditOp.none + : EditOp.substitute; + --i; + --j; + break; + case 1: + result ~= EditOp.insert; + --j; + break; + default: + result ~= EditOp.remove; + --i; + break; + } + } + reverse(result); + return result; + } + +private: + CostType _deletionIncrement = 1, + _insertionIncrement = 1, + _substitutionIncrement = 1; + CostType[] _matrix; + size_t rows, cols; + + // Treat _matrix as a rectangular array + ref CostType matrix(size_t row, size_t col) { return _matrix[row * cols + col]; } + + void AllocMatrix(size_t r, size_t c) { + rows = r; + cols = c; + if (_matrix.length < r * c) { + delete _matrix; + _matrix = new CostType[r * c]; + InitMatrix(); + } + } + + void InitMatrix() { + foreach (r; 0 .. rows) + matrix(r,0) = r * _deletionIncrement; + foreach (c; 0 .. cols) + matrix(0,c) = c * _insertionIncrement; + } + + static uint min_index(CostType i0, CostType i1, CostType i2) + { + if (i0 <= i1) + { + return i0 <= i2 ? 0 : 2; + } + else + { + return i1 <= i2 ? 1 : 2; + } + } + + CostType distanceWithPath(Range s, Range t) + { + auto slen = walkLength(s.save), tlen = walkLength(t.save); + AllocMatrix(slen + 1, tlen + 1); + foreach (i; 1 .. rows) + { + auto sfront = s.front; + auto tt = t.save; + foreach (j; 1 .. cols) + { + auto cSub = matrix(i - 1,j - 1) + + (equals(sfront, tt.front) ? 0 : _substitutionIncrement); + tt.popFront(); + auto cIns = matrix(i,j - 1) + _insertionIncrement; + auto cDel = matrix(i - 1,j) + _deletionIncrement; + switch (min_index(cSub, cIns, cDel)) + { + case 0: + matrix(i,j) = cSub; + break; + case 1: + matrix(i,j) = cIns; + break; + default: + matrix(i,j) = cDel; + break; + } + } + s.popFront(); + } + return matrix(slen,tlen); + } + + CostType distanceLowMem(Range s, Range t, CostType slen, CostType tlen) + { + CostType lastdiag, olddiag; + AllocMatrix(slen + 1, 1); + foreach (y; 1 .. slen + 1) + { + _matrix[y] = y; + } + foreach (x; 1 .. tlen + 1) + { + auto tfront = t.front; + auto ss = s.save; + _matrix[0] = x; + lastdiag = x - 1; + foreach (y; 1 .. rows) + { + olddiag = _matrix[y]; + auto cSub = lastdiag + (equals(ss.front, tfront) ? 0 : _substitutionIncrement); + ss.popFront(); + auto cIns = _matrix[y - 1] + _insertionIncrement; + auto cDel = _matrix[y] + _deletionIncrement; + switch (min_index(cSub, cIns, cDel)) + { + case 0: + _matrix[y] = cSub; + break; + case 1: + _matrix[y] = cIns; + break; + default: + _matrix[y] = cDel; + break; + } + lastdiag = olddiag; + } + t.popFront(); + } + return _matrix[slen]; + } +} + +/** +Returns the $(WEB wikipedia.org/wiki/Levenshtein_distance, Levenshtein +distance) between $(D s) and $(D t). The Levenshtein distance computes +the minimal amount of edit operations necessary to transform $(D s) +into $(D t). Performs $(BIGOH s.length * t.length) evaluations of $(D +equals) and occupies $(BIGOH s.length * t.length) storage. + +Allocates GC memory. +*/ +size_t levenshteinDistance(alias equals = "a == b", Range1, Range2) + (Range1 s, Range2 t) + if (isForwardRange!(Range1) && isForwardRange!(Range2)) +{ + alias eq = binaryFun!(equals); + + for (;;) + { + if (s.empty) return t.walkLength; + if (t.empty) return s.walkLength; + if (eq(s.front, t.front)) + { + s.popFront(); + t.popFront(); + continue; + } + static if (isBidirectionalRange!(Range1) && isBidirectionalRange!(Range2)) + { + if (eq(s.back, t.back)) + { + s.popBack(); + t.popBack(); + continue; + } + } + break; + } + + auto slen = walkLength(s.save); + auto tlen = walkLength(t.save); + + if (slen == 1 && tlen == 1) + { + return eq(s.front, t.front) ? 0 : 1; + } + + if (slen > tlen) + { + Levenshtein!(Range1, eq, size_t) lev; + return lev.distanceLowMem(s, t, slen, tlen); + } + else + { + Levenshtein!(Range2, eq, size_t) lev; + return lev.distanceLowMem(t, s, tlen, slen); + } +} + +/// +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.uni : toUpper; + + assert(levenshteinDistance("cat", "rat") == 1); + assert(levenshteinDistance("parks", "spark") == 2); + assert(levenshteinDistance("abcde", "abcde") == 0); + assert(levenshteinDistance("abcde", "abCde") == 1); + assert(levenshteinDistance("kitten", "sitting") == 3); + assert(levenshteinDistance!((a, b) => std.uni.toUpper(a) == std.uni.toUpper(b)) + ("parks", "SPARK") == 2); + assert(levenshteinDistance("parks".filter!"true", "spark".filter!"true") == 2); + assert(levenshteinDistance("ID", "I♥D") == 1); +} + +/** +Returns the Levenshtein distance and the edit path between $(D s) and +$(D t). + +Allocates GC memory. +*/ +Tuple!(size_t, EditOp[]) +levenshteinDistanceAndPath(alias equals = "a == b", Range1, Range2) + (Range1 s, Range2 t) + if (isForwardRange!(Range1) && isForwardRange!(Range2)) +{ + Levenshtein!(Range1, binaryFun!(equals)) lev; + auto d = lev.distanceWithPath(s, t); + return tuple(d, lev.path()); +} + +/// +@safe unittest +{ + string a = "Saturday", b = "Sunday"; + auto p = levenshteinDistanceAndPath(a, b); + assert(p[0] == 3); + assert(equal(p[1], "nrrnsnnn")); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + assert(levenshteinDistance("a", "a") == 0); + assert(levenshteinDistance("a", "b") == 1); + assert(levenshteinDistance("aa", "ab") == 1); + assert(levenshteinDistance("aa", "abc") == 2); + assert(levenshteinDistance("Saturday", "Sunday") == 3); + assert(levenshteinDistance("kitten", "sitting") == 3); +} + +// max +/** +Returns the maximum of the passed-in values. +*/ +MaxType!T max(T...)(T args) + if (T.length >= 2) +{ + //Get "a" + static if (T.length <= 2) + alias args[0] a; + else + auto a = max(args[0 .. ($+1)/2]); + alias typeof(a) T0; + + //Get "b" + static if (T.length <= 3) + alias args[$-1] b; + else + auto b = max(args[($+1)/2 .. $]); + alias typeof(b) T1; + + static assert (is(typeof(a < b)), + algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); + + //Do the "max" proper with a and b + import std.functional : lessThan; + immutable chooseB = lessThan!(T0, T1)(a, b); + return cast(typeof(return)) (chooseB ? b : a); +} + +/// +@safe unittest +{ + int a = 5; + short b = 6; + double c = 2; + auto d = max(a, b); + assert(is(typeof(d) == int)); + assert(d == 6); + auto e = min(a, b, c); + assert(is(typeof(e) == double)); + assert(e == 2); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int a = 5; + short b = 6; + double c = 2; + auto d = max(a, b); + static assert(is(typeof(d) == int)); + assert(d == 6); + auto e = max(a, b, c); + static assert(is(typeof(e) == double)); + assert(e == 6); + // mixed sign + a = -5; + uint f = 5; + static assert(is(typeof(max(a, f)) == uint)); + assert(max(a, f) == 5); + + //Test user-defined types + import std.datetime; + assert(max(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(2012, 12, 21)); + assert(max(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(2012, 12, 21)); + assert(max(Date(1982, 1, 4), Date.min) == Date(1982, 1, 4)); + assert(max(Date.min, Date(1982, 1, 4)) == Date(1982, 1, 4)); + assert(max(Date(1982, 1, 4), Date.max) == Date.max); + assert(max(Date.max, Date(1982, 1, 4)) == Date.max); + assert(max(Date.min, Date.max) == Date.max); + assert(max(Date.max, Date.min) == Date.max); +} + +// MinType +private template MinType(T...) + if (T.length >= 1) +{ + static if (T.length == 1) + { + alias MinType = T[0]; + } + else static if (T.length == 2) + { + static if (!is(typeof(T[0].min))) + alias MinType = CommonType!T; + else + { + enum hasMostNegative = is(typeof(mostNegative!(T[0]))) && + is(typeof(mostNegative!(T[1]))); + static if (hasMostNegative && mostNegative!(T[1]) < mostNegative!(T[0])) + alias MinType = T[1]; + else static if (hasMostNegative && mostNegative!(T[1]) > mostNegative!(T[0])) + alias MinType = T[0]; + else static if (T[1].max < T[0].max) + alias MinType = T[1]; + else + alias MinType = T[0]; + } + } + else + { + alias MinType = MinType!(MinType!(T[0 .. ($+1)/2]), MinType!(T[($+1)/2 .. $])); + } +} + +// min +/** +Returns the minimum of the passed-in values. +*/ +MinType!T min(T...)(T args) + if (T.length >= 2) +{ + //Get "a" + static if (T.length <= 2) + alias args[0] a; + else + auto a = min(args[0 .. ($+1)/2]); + alias typeof(a) T0; + + //Get "b" + static if (T.length <= 3) + alias args[$-1] b; + else + auto b = min(args[($+1)/2 .. $]); + alias typeof(b) T1; + + static assert (is(typeof(a < b)), + algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); + + //Do the "min" proper with a and b + import std.functional : lessThan; + immutable chooseA = lessThan!(T0, T1)(a, b); + return cast(typeof(return)) (chooseA ? a : b); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int a = 5; + short b = 6; + double c = 2; + auto d = min(a, b); + static assert(is(typeof(d) == int)); + assert(d == 5); + auto e = min(a, b, c); + static assert(is(typeof(e) == double)); + assert(e == 2); + // mixed signedness test + a = -10; + uint f = 10; + static assert(is(typeof(min(a, f)) == int)); + assert(min(a, f) == -10); + + //Test user-defined types + import std.datetime; + assert(min(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(1982, 1, 4)); + assert(min(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(1982, 1, 4)); + assert(min(Date(1982, 1, 4), Date.min) == Date.min); + assert(min(Date.min, Date(1982, 1, 4)) == Date.min); + assert(min(Date(1982, 1, 4), Date.max) == Date(1982, 1, 4)); + assert(min(Date.max, Date(1982, 1, 4)) == Date(1982, 1, 4)); + assert(min(Date.min, Date.max) == Date.min); + assert(min(Date.max, Date.min) == Date.min); +} + +// mismatch +/** +Sequentially compares elements in $(D r1) and $(D r2) in lockstep, and +stops at the first mismatch (according to $(D pred), by default +equality). Returns a tuple with the reduced ranges that start with the +two mismatched values. Performs $(BIGOH min(r1.length, r2.length)) +evaluations of $(D pred). + +See_Also: + $(WEB sgi.com/tech/stl/_mismatch.html, STL's _mismatch) +*/ +Tuple!(Range1, Range2) +mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2) + if (isInputRange!(Range1) && isInputRange!(Range2)) +{ + for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) + { + if (!binaryFun!(pred)(r1.front, r2.front)) break; + } + return tuple(r1, r2); +} + +/// +@safe unittest +{ + int[] x = [ 1, 5, 2, 7, 4, 3 ]; + double[] y = [ 1.0, 5, 2, 7.3, 4, 8 ]; + auto m = mismatch(x, y); + assert(m[0] == x[3 .. $]); + assert(m[1] == y[3 .. $]); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = [ 1, 2, 3 ]; + int[] b = [ 1, 2, 4, 5 ]; + auto mm = mismatch(a, b); + assert(mm[0] == [3]); + assert(mm[1] == [4, 5]); +} + +/** +Returns one of a collection of expressions based on the value of the switch +expression. + +$(D choices) needs to be composed of pairs of test expressions and return +expressions. Each test-expression is compared with $(D switchExpression) using +$(D pred)($(D switchExpression) is the first argument) and if that yields true +- the return expression is returned. + +Both the test and the return expressions are lazily evaluated. + +Params: + +switchExpression = The first argument for the predicate. + +choices = Pairs of test expressions and return expressions. The test +expressions will be the second argument for the predicate, and the return +expression will be returned if the predicate yields true with $(D +switchExpression) and the test expression as arguments. May also have a +default return expression, that needs to be the last expression without a test +expression before it. A return expression may be of void type only if it +always throws. + +Returns: The return expression associated with the first test expression that +made the predicate yield true, or the default return expression if no test +expression matched. + +Throws: If there is no default return expression and the predicate does not +yield true with any test expression - $(D SwitchError) is thrown. $(D +SwitchError) is also thrown if a void return expression was executed without +throwing anything. +*/ +auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choices) +{ + import core.exception : SwitchError; + alias predicate = binaryFun!(pred); + + foreach (index, ChoiceType; R) + { + //The even places in `choices` are for the predicate. + static if (index % 2 == 1) + { + if (predicate(switchExpression, choices[index - 1]())) + { + static if (is(typeof(choices[index]()) == void)) + { + choices[index](); + throw new SwitchError("Choices that return void should throw"); + } + else + { + return choices[index](); + } + } + } + } + + //In case nothing matched: + static if (R.length % 2 == 1) //If there is a default return expression: + { + static if (is(typeof(choices[$ - 1]()) == void)) + { + choices[$ - 1](); + throw new SwitchError("Choices that return void should throw"); + } + else + { + return choices[$ - 1](); + } + } + else //If there is no default return expression: + { + throw new SwitchError("Input not matched by any pattern"); + } +} + +/// +@safe unittest +{ + string res = 2.predSwitch!"a < b"( + 1, "less than 1", + 5, "less than 5", + 10, "less than 10", + "greater or equal to 10"); + + assert(res == "less than 5"); + + //The arguments are lazy, which allows us to use predSwitch to create + //recursive functions: + int factorial(int n) + { + return n.predSwitch!"a <= b"( + -1, {throw new Exception("Can not calculate n! for n < 0");}(), + 0, 1, // 0! = 1 + n * factorial(n - 1) // n! = n * (n - 1)! for n >= 0 + ); + } + assert(factorial(3) == 6); + + //Void return expressions are allowed if they always throw: + import std.exception : assertThrown; + assertThrown!Exception(factorial(-9)); +} + +unittest +{ + import core.exception : SwitchError; + import std.exception : assertThrown; + + //Nothing matches - with default return expression: + assert(20.predSwitch!"a < b"( + 1, "less than 1", + 5, "less than 5", + 10, "less than 10", + "greater or equal to 10") == "greater or equal to 10"); + + //Nothing matches - without default return expression: + assertThrown!SwitchError(20.predSwitch!"a < b"( + 1, "less than 1", + 5, "less than 5", + 10, "less than 10", + )); + + //Using the default predicate: + assert(2.predSwitch( + 1, "one", + 2, "two", + 3, "three", + ) == "two"); + + //Void return expressions must always throw: + assertThrown!SwitchError(1.predSwitch( + 0, "zero", + 1, {}(), //A void return expression that doesn't throw + 2, "two", + )); +} diff --git a/std/algorithm/iteration.d b/std/algorithm/iteration.d new file mode 100644 index 00000000000..a94fcd78005 --- /dev/null +++ b/std/algorithm/iteration.d @@ -0,0 +1,4138 @@ +// Written in the D programming language. +/** +This is a submodule of $(LINK2 std_algorithm_package.html, std.algorithm). +It contains generic _iteration algorithms. + +$(BOOKTABLE Cheat Sheet, + +$(TR $(TH Function Name) $(TH Description)) + +$(T2 aggregate, + $(D [[3, 1, 5], [2, 6, 4]].aggregate!max) returns a range containing + the elements $(D 5) and $(D 6).) +$(T2 cache, + Eagerly evaluates and caches another range's $(D front).) +$(T2 cacheBidirectional, + As above, but also provides $(D back) and $(D popBack).) +$(T2 each, + $(D each!writeln([1, 2, 3])) eagerly prints the numbers $(D 1), $(D 2) + and $(D 3) on their own lines.) +$(T2 filter, + $(D filter!"a > 0"([1, -1, 2, 0, -3])) iterates over elements $(D 1) + and $(D 2).) +$(T2 filterBidirectional, + Similar to $(D filter), but also provides $(D back) and $(D popBack) at + a small increase in cost.) +$(T2 group, + $(D group([5, 2, 2, 3, 3])) returns a range containing the tuples + $(D tuple(5, 1)), $(D tuple(2, 2)), and $(D tuple(3, 2)).) +$(T2 groupBy, + $(D groupBy!((a,b) => a[1] == b[1])([[1, 1], [1, 2], [2, 2], [2, 1]])) + returns a range containing 3 subranges: the first with just + $(D [1, 1]); the second with the elements $(D [1, 2]) and $(D [2, 2]); + and the third with just $(D [2, 1]).) +$(T2 joiner, + $(D joiner(["hello", "world!"], "; ")) returns a range that iterates + over the characters $(D "hello; world!"). No new string is created - + the existing inputs are iterated.) +$(T2 map, + $(D map!"2 * a"([1, 2, 3])) lazily returns a range with the numbers + $(D 2), $(D 4), $(D 6).) +$(T2 reduce, + $(D reduce!"a + b"([1, 2, 3, 4])) returns $(D 10).) +$(T2 splitter, + Lazily splits a range by a separator.) +$(T2 sum, + Same as $(D reduce), but specialized for accurate summation.) +$(T2 uniq, + Iterates over the unique elements in a range, which is assumed sorted.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_iteration.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.iteration; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +import std.traits; + +/// +template aggregate(fun...) if (fun.length >= 1) +{ + /** + * Aggregates elements in each subrange of the given range of ranges using + * the given aggregating function(s). + * Params: + * fun = One or more aggregating functions (binary functions that return a + * single _aggregate value of their arguments). + * ror = A range of ranges to be aggregated. + * + * Returns: + * A range representing the aggregated value(s) of each subrange + * of the original range. If only one aggregating function is specified, + * each element will be the aggregated value itself; if multiple functions + * are specified, each element will be a tuple of the aggregated values of + * each respective function. + */ + auto aggregate(RoR)(RoR ror) + if (isInputRange!RoR && isIterable!(ElementType!RoR)) + { + return ror.map!(reduce!fun); + } + + /// + unittest + { + import std.algorithm.comparison : equal, max, min; + + auto data = [[4, 2, 1, 3], [4, 9, -1, 3, 2], [3]]; + + // Single aggregating function + auto agg1 = data.aggregate!max; + assert(agg1.equal([4, 9, 3])); + + // Multiple aggregating functions + import std.typecons : tuple; + auto agg2 = data.aggregate!(max, min); + assert(agg2.equal([ + tuple(4, 1), + tuple(9, -1), + tuple(3, 3) + ])); + } +} + +/++ +$(D cache) eagerly evaluates $(D front) of $(D range) +on each construction or call to $(D popFront), +to store the result in a cache. +The result is then directly returned when $(D front) is called, +rather than re-evaluated. + +This can be a useful function to place in a chain, after functions +that have expensive evaluation, as a lazy alternative to $(XREF array,array). +In particular, it can be placed after a call to $(D map), or before a call +to $(D filter). + +$(D cache) may provide bidirectional iteration if needed, but since +this comes at an increased cost, it must be explicitly requested via the +call to $(D cacheBidirectional). Furthermore, a bidirectional cache will +evaluate the "center" element twice, when there is only one element left in +the range. + +$(D cache) does not provide random access primitives, +as $(D cache) would be unable to cache the random accesses. +If $(D Range) provides slicing primitives, +then $(D cache) will provide the same slicing primitives, +but $(D hasSlicing!Cache) will not yield true (as the $(XREF range,hasSlicing) +trait also checks for random access). ++/ +auto cache(Range)(Range range) +if (isInputRange!Range) +{ + return Cache!(Range, false)(range); +} + +/// ditto +auto cacheBidirectional(Range)(Range range) +if (isBidirectionalRange!Range) +{ + return Cache!(Range, true)(range); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.stdio, std.range; + import std.typecons : tuple; + + ulong counter = 0; + double fun(int x) + { + ++counter; + // http://en.wikipedia.org/wiki/Quartic_function + return ( (x + 4.0) * (x + 1.0) * (x - 1.0) * (x - 3.0) ) / 14.0 + 0.5; + } + // Without cache, with array (greedy) + auto result1 = iota(-4, 5).map!(a =>tuple(a, fun(a)))() + .filter!"a[1]<0"() + .map!"a[0]"() + .array(); + + // the values of x that have a negative y are: + assert(equal(result1, [-3, -2, 2])); + + // Check how many times fun was evaluated. + // As many times as the number of items in both source and result. + assert(counter == iota(-4, 5).length + result1.length); + + counter = 0; + // Without array, with cache (lazy) + auto result2 = iota(-4, 5).map!(a =>tuple(a, fun(a)))() + .cache() + .filter!"a[1]<0"() + .map!"a[0]"(); + + // the values of x that have a negative y are: + assert(equal(result2, [-3, -2, 2])); + + // Check how many times fun was evaluated. + // Only as many times as the number of items in source. + assert(counter == iota(-4, 5).length); +} + +/++ +Tip: $(D cache) is eager when evaluating elements. If calling front on the +underlying range has a side effect, it will be observeable before calling +front on the actual cached range. + +Furtermore, care should be taken composing $(D cache) with $(XREF range,take). +By placing $(D take) before $(D cache), then $(D cache) will be "aware" +of when the range ends, and correctly stop caching elements when needed. +If calling front has no side effect though, placing $(D take) after $(D cache) +may yield a faster range. + +Either way, the resulting ranges will be equivalent, but maybe not at the +same cost or side effects. ++/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + int i = 0; + + auto r = iota(0, 4).tee!((a){i = a;}, No.pipeOnPop); + auto r1 = r.take(3).cache(); + auto r2 = r.cache().take(3); + + assert(equal(r1, [0, 1, 2])); + assert(i == 2); //The last "seen" element was 2. The data in cache has been cleared. + + assert(equal(r2, [0, 1, 2])); + assert(i == 3); //cache has accessed 3. It is still stored internally by cache. +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + auto a = [1, 2, 3, 4]; + assert(equal(a.map!"(a - 1)*a"().cache(), [ 0, 2, 6, 12])); + assert(equal(a.map!"(a - 1)*a"().cacheBidirectional().retro(), [12, 6, 2, 0])); + auto r1 = [1, 2, 3, 4].cache() [1 .. $]; + auto r2 = [1, 2, 3, 4].cacheBidirectional()[1 .. $]; + assert(equal(r1, [2, 3, 4])); + assert(equal(r2, [2, 3, 4])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + //immutable test + static struct S + { + int i; + this(int i) + { + //this.i = i; + } + } + immutable(S)[] s = [S(1), S(2), S(3)]; + assert(equal(s.cache(), s)); + assert(equal(s.cacheBidirectional(), s)); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + //safety etc + auto a = [1, 2, 3, 4]; + assert(equal(a.cache(), a)); + assert(equal(a.cacheBidirectional(), a)); +} + +@safe unittest +{ + char[][] stringbufs = ["hello".dup, "world".dup]; + auto strings = stringbufs.map!((a)=>a.idup)().cache(); + assert(strings.front is strings.front); +} + +@safe unittest +{ + import std.range; + auto c = [1, 2, 3].cycle().cache(); + c = c[1 .. $]; + auto d = c[0 .. 1]; +} + +@safe unittest +{ + static struct Range + { + bool initialized = false; + bool front() @property {return initialized = true;} + void popFront() {initialized = false;} + enum empty = false; + } + auto r = Range().cache(); + assert(r.source.initialized == true); +} + +private struct Cache(R, bool bidir) +{ + import core.exception : RangeError; + + private + { + import std.typetuple : TypeTuple; + + alias E = ElementType!R; + alias UE = Unqual!E; + + R source; + + static if (bidir) alias CacheTypes = TypeTuple!(UE, UE); + else alias CacheTypes = TypeTuple!UE; + CacheTypes caches; + + static assert(isAssignable!(UE, E) && is(UE : E), + algoFormat("Cannot instantiate range with %s because %s elements are not assignable to %s.", R.stringof, E.stringof, UE.stringof)); + } + + this(R range) + { + source = range; + if (!range.empty) + { + caches[0] = source.front; + static if (bidir) + caches[1] = source.back; + } + } + + static if (isInfinite!R) + enum empty = false; + else + bool empty() @property + { + return source.empty; + } + + static if (hasLength!R) auto length() @property + { + return source.length; + } + + E front() @property + { + version(assert) if (empty) throw new RangeError(); + return caches[0]; + } + static if (bidir) E back() @property + { + version(assert) if (empty) throw new RangeError(); + return caches[1]; + } + + void popFront() + { + version(assert) if (empty) throw new RangeError(); + source.popFront(); + if (!source.empty) + caches[0] = source.front; + else + caches = CacheTypes.init; + } + static if (bidir) void popBack() + { + version(assert) if (empty) throw new RangeError(); + source.popBack(); + if (!source.empty) + caches[1] = source.back; + else + caches = CacheTypes.init; + } + + static if (isForwardRange!R) + { + private this(R source, ref CacheTypes caches) + { + this.source = source; + this.caches = caches; + } + typeof(this) save() @property + { + return typeof(this)(source.save, caches); + } + } + + static if (hasSlicing!R) + { + enum hasEndSlicing = is(typeof(source[size_t.max .. $])); + + static if (hasEndSlicing) + { + private static struct DollarToken{} + enum opDollar = DollarToken.init; + + auto opSlice(size_t low, DollarToken) + { + return typeof(this)(source[low .. $]); + } + } + + static if (!isInfinite!R) + { + typeof(this) opSlice(size_t low, size_t high) + { + return typeof(this)(source[low .. high]); + } + } + else static if (hasEndSlicing) + { + auto opSlice(size_t low, size_t high) + in + { + assert(low <= high); + } + body + { + import std.range : take; + return this[low .. $].take(high - low); + } + } + } +} + +/** +$(D auto map(Range)(Range r) if (isInputRange!(Unqual!Range));) + +Implements the homonym function (also known as $(D transform)) present +in many languages of functional flavor. The call $(D map!(fun)(range)) +returns a range of which elements are obtained by applying $(D fun(a)) +left to right for all elements $(D a) in $(D range). The original ranges are +not changed. Evaluation is done lazily. + +See_Also: + $(WEB en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) +*/ +template map(fun...) if (fun.length >= 1) +{ + auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) + { + import std.typetuple : staticMap; + + alias AppliedReturnType(alias f) = typeof(f(r.front)); + + static if (fun.length > 1) + { + import std.functional : adjoin; + import std.typetuple : staticIndexOf; + + alias _funs = staticMap!(unaryFun, fun); + alias _fun = adjoin!_funs; + + alias ReturnTypes = staticMap!(AppliedReturnType, _funs); + static assert(staticIndexOf!(void, ReturnTypes) == -1, + "All mapping functions must not return void."); + } + else + { + alias _fun = unaryFun!fun; + + static assert(!is(AppliedReturnType!_fun == void), + "Mapping function must not return void."); + } + + return MapResult!(_fun, Range)(r); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : chain; + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] arr2 = [ 5, 6 ]; + auto squares = map!(a => a * a)(chain(arr1, arr2)); + assert(equal(squares, [ 1, 4, 9, 16, 25, 36 ])); +} + +/** +Multiple functions can be passed to $(D map). In that case, the +element type of $(D map) is a tuple containing one element for each +function. +*/ +@safe unittest +{ + auto sums = [2, 4, 6, 8]; + auto products = [1, 4, 9, 16]; + + size_t i = 0; + foreach (result; [ 1, 2, 3, 4 ].map!("a + a", "a * a")) + { + assert(result[0] == sums[i]); + assert(result[1] == products[i]); + ++i; + } +} + +/** +You may alias $(D map) with some function(s) to a symbol and use +it separately: +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + + alias stringize = map!(to!string); + assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); +} + +private struct MapResult(alias fun, Range) +{ + alias R = Unqual!Range; + R _input; + + static if (isBidirectionalRange!R) + { + @property auto ref back()() + { + return fun(_input.back); + } + + void popBack()() + { + _input.popBack(); + } + } + + this(R input) + { + _input = input; + } + + static if (isInfinite!R) + { + // Propagate infinite-ness. + enum bool empty = false; + } + else + { + @property bool empty() + { + return _input.empty; + } + } + + void popFront() + { + _input.popFront(); + } + + @property auto ref front() + { + return fun(_input.front); + } + + static if (isRandomAccessRange!R) + { + static if (is(typeof(_input[ulong.max]))) + private alias opIndex_t = ulong; + else + private alias opIndex_t = uint; + + auto ref opIndex(opIndex_t index) + { + return fun(_input[index]); + } + } + + static if (hasLength!R) + { + @property auto length() + { + return _input.length; + } + + alias opDollar = length; + } + + static if (hasSlicing!R) + { + static if (is(typeof(_input[ulong.max .. ulong.max]))) + private alias opSlice_t = ulong; + else + private alias opSlice_t = uint; + + static if (hasLength!R) + { + auto opSlice(opSlice_t low, opSlice_t high) + { + return typeof(this)(_input[low .. high]); + } + } + else static if (is(typeof(_input[opSlice_t.max .. $]))) + { + struct DollarToken{} + enum opDollar = DollarToken.init; + auto opSlice(opSlice_t low, DollarToken) + { + return typeof(this)(_input[low .. $]); + } + + auto opSlice(opSlice_t low, opSlice_t high) + { + import std.range : take; + return this[low .. $].take(high - low); + } + } + } + + static if (isForwardRange!R) + { + @property auto save() + { + return typeof(this)(_input.save); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + import std.functional : adjoin; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + alias stringize = map!(to!string); + assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); + + uint counter; + alias count = map!((a) { return counter++; }); + assert(equal(count([ 10, 2, 30, 4 ]), [ 0, 1, 2, 3 ])); + + counter = 0; + adjoin!((a) { return counter++; }, (a) { return counter++; })(1); + alias countAndSquare = map!((a) { return counter++; }, (a) { return counter++; }); + //assert(equal(countAndSquare([ 10, 2 ]), [ tuple(0u, 100), tuple(1u, 4) ])); +} + +unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.ascii : toUpper; + import std.range; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] arr1 = [ 1, 2, 3, 4 ]; + const int[] arr1Const = arr1; + int[] arr2 = [ 5, 6 ]; + auto squares = map!("a * a")(arr1Const); + assert(squares[$ - 1] == 16); + assert(equal(squares, [ 1, 4, 9, 16 ][])); + assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][])); + + // Test the caching stuff. + assert(squares.back == 16); + auto squares2 = squares.save; + assert(squares2.back == 16); + + assert(squares2.front == 1); + squares2.popFront(); + assert(squares2.front == 4); + squares2.popBack(); + assert(squares2.front == 4); + assert(squares2.back == 9); + + assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][])); + + uint i; + foreach (e; map!("a", "a * a")(arr1)) + { + assert(e[0] == ++i); + assert(e[1] == i * i); + } + + // Test length. + assert(squares.length == 4); + assert(map!"a * a"(chain(arr1, arr2)).length == 6); + + // Test indexing. + assert(squares[0] == 1); + assert(squares[1] == 4); + assert(squares[2] == 9); + assert(squares[3] == 16); + + // Test slicing. + auto squareSlice = squares[1..squares.length - 1]; + assert(equal(squareSlice, [4, 9][])); + assert(squareSlice.back == 9); + assert(squareSlice[1] == 9); + + // Test on a forward range to make sure it compiles when all the fancy + // stuff is disabled. + auto fibsSquares = map!"a * a"(recurrence!("a[n-1] + a[n-2]")(1, 1)); + assert(fibsSquares.front == 1); + fibsSquares.popFront(); + fibsSquares.popFront(); + assert(fibsSquares.front == 4); + fibsSquares.popFront(); + assert(fibsSquares.front == 9); + + auto repeatMap = map!"a"(repeat(1)); + static assert(isInfinite!(typeof(repeatMap))); + + auto intRange = map!"a"([1,2,3]); + static assert(isRandomAccessRange!(typeof(intRange))); + + foreach (DummyType; AllDummyRanges) + { + DummyType d; + auto m = map!"a * a"(d); + + static assert(propagatesRangeType!(typeof(m), DummyType)); + assert(equal(m, [1,4,9,16,25,36,49,64,81,100])); + } + + //Test string access + string s1 = "hello world!"; + dstring s2 = "日本語"; + dstring s3 = "hello world!"d; + auto ms1 = map!(std.ascii.toUpper)(s1); + auto ms2 = map!(std.ascii.toUpper)(s2); + auto ms3 = map!(std.ascii.toUpper)(s3); + static assert(!is(ms1[0])); //narrow strings can't be indexed + assert(ms2[0] == '日'); + assert(ms3[0] == 'H'); + static assert(!is(ms1[0..1])); //narrow strings can't be sliced + assert(equal(ms2[0..2], "日本"w)); + assert(equal(ms3[0..2], "HE")); + + // Issue 5753 + static void voidFun(int) {} + static int nonvoidFun(int) { return 0; } + static assert(!__traits(compiles, map!voidFun([1]))); + static assert(!__traits(compiles, map!(voidFun, voidFun)([1]))); + static assert(!__traits(compiles, map!(nonvoidFun, voidFun)([1]))); + static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1]))); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + auto LL = iota(1L, 4L); + auto m = map!"a*a"(LL); + assert(equal(m, [1L, 4L, 9L])); +} + +@safe unittest +{ + import std.range : iota; + + // Issue #10130 - map of iota with const step. + const step = 2; + static assert(__traits(compiles, map!(i => i)(iota(0, 10, step)))); + + // Need these to all by const to repro the float case, due to the + // CommonType template used in the float specialization of iota. + const floatBegin = 0.0; + const floatEnd = 1.0; + const floatStep = 0.02; + static assert(__traits(compiles, map!(i => i)(iota(floatBegin, floatEnd, floatStep)))); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + //slicing infinites + auto rr = iota(0, 5).cycle().map!"a * a"(); + alias RR = typeof(rr); + static assert(hasSlicing!RR); + rr = rr[6 .. $]; //Advances 1 cycle and 1 unit + assert(equal(rr[0 .. 5], [1, 4, 9, 16, 0])); +} + +@safe unittest +{ + import std.range; + struct S {int* p;} + auto m = immutable(S).init.repeat().map!"a".save; +} + +// each +/** +Eagerly iterates over $(D r) and calls $(D pred) over _each element. + +Params: + pred = predicate to apply to each element of the range + r = range or iterable over which each iterates + +Example: +--- +void deleteOldBackups() +{ + import std.algorithm, std.datetime, std.file; + auto cutoff = Clock.currTime() - 7.days; + dirEntries("", "*~", SpanMode.depth) + .filter!(de => de.timeLastModified < cutoff) + .each!remove(); +} +--- + +If the range supports it, the value can be mutated in place. Examples: +--- +arr.each!((ref a) => a++); +arr.each!"a++"; +--- + +If no predicate is specified, $(D each) will default to doing nothing +but consuming the entire range. $(D .front) will be evaluated, but this +can be avoided by explicitly specifying a predicate lambda with a +$(D lazy) parameter. + +$(D each) also supports $(D opApply)-based iterators, so it will work +with e.g. $(XREF parallelism, parallel). + +See_Also: $(XREF range,tee) + + */ +template each(alias pred = "a") +{ + import std.typetuple : TypeTuple; + alias BinaryArgs = TypeTuple!(pred, "i", "a"); + + enum isRangeUnaryIterable(R) = + is(typeof(unaryFun!pred(R.init.front))); + + enum isRangeBinaryIterable(R) = + is(typeof(binaryFun!BinaryArgs(0, R.init.front))); + + enum isRangeIterable(R) = + isInputRange!R && + (isRangeUnaryIterable!R || isRangeBinaryIterable!R); + + enum isForeachUnaryIterable(R) = + is(typeof((R r) { + foreach (ref a; r) + cast(void)unaryFun!pred(a); + })); + + enum isForeachBinaryIterable(R) = + is(typeof((R r) { + foreach (i, ref a; r) + cast(void)binaryFun!BinaryArgs(i, a); + })); + + enum isForeachIterable(R) = + (!isForwardRange!R || isDynamicArray!R) && + (isForeachUnaryIterable!R || isForeachBinaryIterable!R); + + void each(Range)(Range r) + if (isRangeIterable!Range && !isForeachIterable!Range) + { + debug(each) pragma(msg, "Using while for ", Range.stringof); + static if (isRangeUnaryIterable!Range) + { + while (!r.empty) + { + cast(void)unaryFun!pred(r.front); + r.popFront(); + } + } + else // if (isRangeBinaryIterable!Range) + { + size_t i = 0; + while (!r.empty) + { + cast(void)binaryFun!BinaryArgs(i, r.front); + r.popFront(); + i++; + } + } + } + + void each(Iterable)(Iterable r) + if (isForeachIterable!Iterable) + { + debug(each) pragma(msg, "Using foreach for ", Iterable.stringof); + static if (isForeachUnaryIterable!Iterable) + { + foreach (ref e; r) + cast(void)unaryFun!pred(e); + } + else // if (isForeachBinaryIterable!Iterable) + { + foreach (i, ref e; r) + cast(void)binaryFun!BinaryArgs(i, e); + } + } +} + +unittest +{ + import std.range : iota; + + long[] arr; + // Note: each over arrays should resolve to the + // foreach variant, but as this is a performance + // improvement it is not unit-testable. + iota(5).each!(n => arr ~= n); + assert(arr == [0, 1, 2, 3, 4]); + + // in-place mutation + arr.each!((ref n) => n++); + assert(arr == [1, 2, 3, 4, 5]); + + // by-ref lambdas should not be allowed for non-ref ranges + static assert(!is(typeof(arr.map!(n => n).each!((ref n) => n++)))); + + // default predicate (walk / consume) + auto m = arr.map!(n => n); + (&m).each(); + assert(m.empty); + + // in-place mutation with index + arr[] = 0; + arr.each!"a=i"(); + assert(arr == [0, 1, 2, 3, 4]); + + // opApply iterators + static assert(is(typeof({ + import std.parallelism; + arr.parallel.each!"a++"; + }))); +} + +/** +$(D auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range));) + +Implements the higher order _filter function. + +Params: + predicate = Function to apply to each element of range + range = Input range of elements + +Returns: + $(D filter!(predicate)(range)) returns a new range containing only elements $(D x) in $(D range) for + which $(D predicate(x)) returns $(D true). + +See_Also: + $(WEB en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) + */ +template filter(alias predicate) if (is(typeof(unaryFun!predicate))) +{ + auto filter(Range)(Range range) if (isInputRange!(Unqual!Range)) + { + return FilterResult!(unaryFun!predicate, Range)(range); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.math : approxEqual; + import std.range; + + int[] arr = [ 1, 2, 3, 4, 5 ]; + + // Sum all elements + auto small = filter!(a => a < 3)(arr); + assert(equal(small, [ 1, 2 ])); + + // Sum again, but with Uniform Function Call Syntax (UFCS) + auto sum = arr.filter!(a => a < 3); + assert(equal(sum, [ 1, 2 ])); + + // In combination with chain() to span multiple ranges + int[] a = [ 3, -2, 400 ]; + int[] b = [ 100, -101, 102 ]; + auto r = chain(a, b).filter!(a => a > 0); + assert(equal(r, [ 3, 400, 100, 102 ])); + + // Mixing convertible types is fair game, too + double[] c = [ 2.5, 3.0 ]; + auto r1 = chain(c, a, b).filter!(a => cast(int) a != a); + assert(approxEqual(r1, [ 2.5 ])); +} + +private struct FilterResult(alias pred, Range) +{ + alias R = Unqual!Range; + R _input; + + this(R r) + { + _input = r; + while (!_input.empty && !pred(_input.front)) + { + _input.popFront(); + } + } + + auto opSlice() { return this; } + + static if (isInfinite!Range) + { + enum bool empty = false; + } + else + { + @property bool empty() { return _input.empty; } + } + + void popFront() + { + do + { + _input.popFront(); + } while (!_input.empty && !pred(_input.front)); + } + + @property auto ref front() + { + return _input.front; + } + + static if (isForwardRange!R) + { + @property auto save() + { + return typeof(this)(_input.save); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.range; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 3, 4, 2 ]; + auto r = filter!("a > 3")(a); + static assert(isForwardRange!(typeof(r))); + assert(equal(r, [ 4 ][])); + + a = [ 1, 22, 3, 42, 5 ]; + auto under10 = filter!("a < 10")(a); + assert(equal(under10, [1, 3, 5][])); + static assert(isForwardRange!(typeof(under10))); + under10.front = 4; + assert(equal(under10, [4, 3, 5][])); + under10.front = 40; + assert(equal(under10, [40, 3, 5][])); + under10.front = 1; + + auto infinite = filter!"a > 2"(repeat(3)); + static assert(isInfinite!(typeof(infinite))); + static assert(isForwardRange!(typeof(infinite))); + + foreach (DummyType; AllDummyRanges) { + DummyType d; + auto f = filter!"a & 1"(d); + assert(equal(f, [1,3,5,7,9])); + + static if (isForwardRange!DummyType) { + static assert(isForwardRange!(typeof(f))); + } + } + + // With delegates + int x = 10; + int overX(int a) { return a > x; } + typeof(filter!overX(a)) getFilter() + { + return filter!overX(a); + } + auto r1 = getFilter(); + assert(equal(r1, [22, 42])); + + // With chain + auto nums = [0,1,2,3,4]; + assert(equal(filter!overX(chain(a, nums)), [22, 42])); + + // With copying of inner struct Filter to Map + auto arr = [1,2,3,4,5]; + auto m = map!"a + 1"(filter!"a < 4"(arr)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 3, 4 ]; + const aConst = a; + auto r = filter!("a > 3")(aConst); + assert(equal(r, [ 4 ][])); + + a = [ 1, 22, 3, 42, 5 ]; + auto under10 = filter!("a < 10")(a); + assert(equal(under10, [1, 3, 5][])); + assert(equal(under10.save, [1, 3, 5][])); + assert(equal(under10.save, under10)); + + // With copying of inner struct Filter to Map + auto arr = [1,2,3,4,5]; + auto m = map!"a + 1"(filter!"a < 4"(arr)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.functional : compose, pipe; + + assert(equal(compose!(map!"2 * a", filter!"a & 1")([1,2,3,4,5]), + [2,6,10])); + assert(equal(pipe!(filter!"a & 1", map!"2 * a")([1,2,3,4,5]), + [2,6,10])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + int x = 10; + int underX(int a) { return a < x; } + const(int)[] list = [ 1, 2, 10, 11, 3, 4 ]; + assert(equal(filter!underX(list), [ 1, 2, 3, 4 ])); +} + +/** + * $(D auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range));) + * + * Similar to $(D filter), except it defines a bidirectional + * range. There is a speed disadvantage - the constructor spends time + * finding the last element in the range that satisfies the filtering + * condition (in addition to finding the first one). The advantage is + * that the filtered range can be spanned from both directions. Also, + * $(XREF range, retro) can be applied against the filtered range. + * + * Params: + * pred = Function to apply to each element of range + * r = Bidirectional range of elements + */ +template filterBidirectional(alias pred) +{ + auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range)) + { + return FilterBidiResult!(unaryFun!pred, Range)(r); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + + int[] arr = [ 1, 2, 3, 4, 5 ]; + auto small = filterBidirectional!("a < 3")(arr); + static assert(isBidirectionalRange!(typeof(small))); + assert(small.back == 2); + assert(equal(small, [ 1, 2 ])); + assert(equal(retro(small), [ 2, 1 ])); + // In combination with chain() to span multiple ranges + int[] a = [ 3, -2, 400 ]; + int[] b = [ 100, -101, 102 ]; + auto r = filterBidirectional!("a > 0")(chain(a, b)); + assert(r.back == 102); +} + +private struct FilterBidiResult(alias pred, Range) +{ + alias R = Unqual!Range; + R _input; + + this(R r) + { + _input = r; + while (!_input.empty && !pred(_input.front)) _input.popFront(); + while (!_input.empty && !pred(_input.back)) _input.popBack(); + } + + @property bool empty() { return _input.empty; } + + void popFront() + { + do + { + _input.popFront(); + } while (!_input.empty && !pred(_input.front)); + } + + @property auto ref front() + { + return _input.front; + } + + void popBack() + { + do + { + _input.popBack(); + } while (!_input.empty && !pred(_input.back)); + } + + @property auto ref back() + { + return _input.back; + } + + @property auto save() + { + return typeof(this)(_input.save); + } +} + +// group +struct Group(alias pred, R) if (isInputRange!R) +{ + import std.typecons : Rebindable, tuple, Tuple; + + private alias comp = binaryFun!pred; + + private alias E = ElementType!R; + static if ((is(E == class) || is(E == interface)) && + (is(E == const) || is(E == immutable))) + { + private alias MutableE = Rebindable!E; + } + else static if (is(E : Unqual!E)) + { + private alias MutableE = Unqual!E; + } + else + { + private alias MutableE = E; + } + + private R _input; + private Tuple!(MutableE, uint) _current; + + this(R input) + { + _input = input; + if (!_input.empty) popFront(); + } + + void popFront() + { + if (_input.empty) + { + _current[1] = 0; + } + else + { + _current = tuple(_input.front, 1u); + _input.popFront(); + while (!_input.empty && comp(_current[0], _input.front)) + { + ++_current[1]; + _input.popFront(); + } + } + } + + static if (isInfinite!R) + { + enum bool empty = false; // Propagate infiniteness. + } + else + { + @property bool empty() + { + return _current[1] == 0; + } + } + + @property auto ref front() + { + assert(!empty); + return _current; + } + + static if (isForwardRange!R) { + @property typeof(this) save() { + typeof(this) ret = this; + ret._input = this._input.save; + ret._current = this._current; + return ret; + } + } +} + +/** +Groups consecutively equivalent elements into a single tuple of the element and +the number of its repetitions. + +Similarly to $(D uniq), $(D group) produces a range that iterates over unique +consecutive elements of the given range. Each element of this range is a tuple +of the element and the number of times it is repeated in the original range. +Equivalence of elements is assessed by using the predicate $(D pred), which +defaults to $(D "a == b"). + +Params: + pred = Binary predicate for determining equivalence of two elements. + r = The $(XREF2 range, isInputRange, input range) to iterate over. + +Returns: A range of elements of type $(D Tuple!(ElementType!R, uint)), +representing each consecutively unique element and its respective number of +occurrences in that run. This will be an input range if $(D R) is an input +range, and a forward range in all other cases. +*/ +Group!(pred, Range) group(alias pred = "a == b", Range)(Range r) +{ + return typeof(return)(r); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple, Tuple; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u), + tuple(4, 3u), tuple(5, 1u) ][])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.typecons : tuple, Tuple; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u), + tuple(4, 3u), tuple(5, 1u) ][])); + static assert(isForwardRange!(typeof(group(arr)))); + + foreach (DummyType; AllDummyRanges) { + DummyType d; + auto g = group(d); + + static assert(d.rt == RangeType.Input || isForwardRange!(typeof(g))); + + assert(equal(g, [tuple(1, 1u), tuple(2, 1u), tuple(3, 1u), tuple(4, 1u), + tuple(5, 1u), tuple(6, 1u), tuple(7, 1u), tuple(8, 1u), + tuple(9, 1u), tuple(10, 1u)])); + } +} + +unittest +{ + // Issue 13857 + immutable(int)[] a1 = [1,1,2,2,2,3,4,4,5,6,6,7,8,9,9,9]; + auto g1 = group(a1); + + // Issue 13162 + immutable(ubyte)[] a2 = [1, 1, 1, 0, 0, 0]; + auto g2 = a2.group; + + // Issue 10104 + const a3 = [1, 1, 2, 2]; + auto g3 = a3.group; + + interface I {} + class C : I {} + const C[] a4 = [new const C()]; + auto g4 = a4.group!"a is b"; + + immutable I[] a5 = [new immutable C()]; + auto g5 = a5.group!"a is b"; + + const(int[][]) a6 = [[1], [1]]; + auto g6 = a6.group; +} + +// Used by implementation of groupBy for non-forward input ranges. +private struct GroupByChunkImpl(alias pred, Range) + if (isInputRange!Range && !isForwardRange!Range) +{ + alias fun = binaryFun!pred; + + private Range r; + private ElementType!Range prev; + + this(Range range, ElementType!Range _prev) + { + r = range; + prev = _prev; + } + + @property bool empty() + { + return r.empty || !fun(prev, r.front); + } + + @property ElementType!Range front() { return r.front; } + void popFront() { r.popFront(); } +} + +// Implementation of groupBy for non-forward input ranges. +private struct GroupByImpl(alias pred, Range) + if (isInputRange!Range && !isForwardRange!Range) +{ + alias fun = binaryFun!pred; + + private Range r; + private ElementType!Range _prev; + + this(Range _r) + { + r = _r; + if (!empty) + { + // Check reflexivity if predicate is claimed to be an equivalence + // relation. + assert(pred(r.front, r.front), + "predicate " ~ pred.stringof ~ " is not reflexive"); + + // _prev's type may be a nested struct, so must be initialized + // directly in the constructor (cannot call savePred()). + _prev = r.front; + } + else + { + // We won't use _prev, but must be initialized. + _prev = typeof(_prev).init; + } + } + @property bool empty() { return r.empty; } + + @property auto front() + { + return GroupByChunkImpl!(pred, Range)(r, _prev); + } + + void popFront() + { + while (!r.empty) + { + if (!fun(_prev, r.front)) + { + _prev = r.front; + break; + } + r.popFront(); + } + } +} + +// Single-pass implementation of groupBy for forward ranges. +private struct GroupByImpl(alias pred, Range) + if (isForwardRange!Range) +{ + import std.typecons : RefCounted; + + // Outer range + static struct Impl + { + size_t groupNum; + Range current; + Range next; + } + + // Inner range + static struct Group + { + private size_t groupNum; + private Range start; + private Range current; + + private RefCounted!Impl mothership; + + this(RefCounted!Impl origin) + { + groupNum = origin.groupNum; + + start = origin.current.save; + current = origin.current.save; + assert(!start.empty); + + mothership = origin; + + // Note: this requires reflexivity. + assert(pred(start.front, current.front), + "predicate " ~ pred.stringof ~ " is not reflexive"); + } + + @property bool empty() { return groupNum == size_t.max; } + @property auto ref front() { return current.front; } + + void popFront() + { + current.popFront(); + + // Note: this requires transitivity. + if (current.empty || !pred(start.front, current.front)) + { + if (groupNum == mothership.groupNum) + { + // If parent range hasn't moved on yet, help it along by + // saving location of start of next Group. + mothership.next = current.save; + } + + groupNum = size_t.max; + } + } + + @property auto save() + { + auto copy = this; + copy.current = current.save; + return copy; + } + } + static assert(isForwardRange!Group); + + private RefCounted!Impl impl; + + this(Range r) + { + impl = RefCounted!Impl(0, r, r.save); + } + + @property bool empty() { return impl.current.empty; } + @property auto front() { return Group(impl); } + + void popFront() + { + // Scan for next group. If we're lucky, one of our Groups would have + // already set .next to the start of the next group, in which case the + // loop is skipped. + while (!impl.next.empty && pred(impl.current.front, impl.next.front)) + { + impl.next.popFront(); + } + + impl.current = impl.next.save; + + // Indicate to any remaining Groups that we have moved on. + impl.groupNum++; + } + + @property auto save() + { + // Note: the new copy of the range will be detached from any existing + // satellite Groups, and will not benefit from the .next acceleration. + return typeof(this)(impl.current.save); + } + + static assert(isForwardRange!(typeof(this))); +} + +unittest +{ + import std.algorithm.comparison : equal; + + size_t popCount = 0; + class RefFwdRange + { + int[] impl; + + this(int[] data) { impl = data; } + @property bool empty() { return impl.empty; } + @property auto ref front() { return impl.front; } + void popFront() + { + impl.popFront(); + popCount++; + } + @property auto save() { return new RefFwdRange(impl); } + } + static assert(isForwardRange!RefFwdRange); + + auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9]); + auto groups = testdata.groupBy!((a,b) => (a % 2) == (b % 2)); + auto outerSave1 = groups.save; + + // Sanity test + assert(groups.equal!equal([[1, 3, 5], [2, 4], [7], [6, 8], [9]])); + assert(groups.empty); + + // Performance test for single-traversal use case: popFront should not have + // been called more times than there are elements if we traversed the + // segmented range exactly once. + assert(popCount == 9); + + // Outer range .save test + groups = outerSave1.save; + assert(!groups.empty); + + // Inner range .save test + auto grp1 = groups.front.save; + auto grp1b = grp1.save; + assert(grp1b.equal([1, 3, 5])); + assert(grp1.save.equal([1, 3, 5])); + + // Inner range should remain consistent after outer range has moved on. + groups.popFront(); + assert(grp1.save.equal([1, 3, 5])); + + // Inner range should not be affected by subsequent inner ranges. + assert(groups.front.equal([2, 4])); + assert(grp1.save.equal([1, 3, 5])); +} + +/** + * Chunks an input range into subranges of equivalent adjacent elements. + * + * Equivalence is defined by the predicate $(D pred), which can be either + * binary or unary. In the binary form, two _range elements $(D a) and $(D b) + * are considered equivalent if $(D pred(a,b)) is true. In unary form, two + * elements are considered equivalent if $(D pred(a) == pred(b)) is true. + * + * This predicate must be an equivalence relation, that is, it must be + * reflexive ($(D pred(x,x)) is always true), symmetric + * ($(D pred(x,y) == pred(y,x))), and transitive ($(D pred(x,y) && pred(y,z)) + * implies $(D pred(x,z))). If this is not the case, the range returned by + * groupBy may assert at runtime or behave erratically. + * + * Params: + * pred = Predicate for determining equivalence. + * r = The range to be chunked. + * + * Returns: A range of ranges in which all elements in a given subrange are + * equivalent under the given predicate. + * + * Notes: + * + * Equivalent elements separated by an intervening non-equivalent element will + * appear in separate subranges; this function only considers adjacent + * equivalence. Elements in the subranges will always appear in the same order + * they appear in the original range. + * + * See_also: + * $(XREF algorithm,group), which collapses adjacent equivalent elements into a + * single element. + */ +auto groupBy(alias pred, Range)(Range r) + if (isInputRange!Range) +{ + static if (is(typeof(binaryFun!pred(ElementType!Range.init, + ElementType!Range.init)) : bool)) + return GroupByImpl!(pred, Range)(r); + else static if (is(typeof( + unaryFun!pred(ElementType!Range.init) == + unaryFun!pred(ElementType!Range.init)))) + return GroupByImpl!((a,b) => pred(a) == pred(b), Range)(r); + else + static assert(0, "groupBy expects either a binary predicate or "~ + "a unary predicate on range elements of type: "~ + ElementType!Range.stringof); +} + +/// Showing usage with binary predicate: +/*FIXME: @safe*/ unittest +{ + import std.algorithm.comparison : equal; + + // Grouping by particular attribute of each element: + auto data = [ + [1, 1], + [1, 2], + [2, 2], + [2, 3] + ]; + + auto r1 = data.groupBy!((a,b) => a[0] == b[0]); + assert(r1.equal!equal([ + [[1, 1], [1, 2]], + [[2, 2], [2, 3]] + ])); + + auto r2 = data.groupBy!((a,b) => a[1] == b[1]); + assert(r2.equal!equal([ + [[1, 1]], + [[1, 2], [2, 2]], + [[2, 3]] + ])); +} + +version(none) // this example requires support for non-equivalence relations +unittest +{ + auto data = [ + [1, 1], + [1, 2], + [2, 2], + [2, 3] + ]; + + version(none) + { + // Grouping by maximum adjacent difference: + import std.math : abs; + auto r3 = [1, 3, 2, 5, 4, 9, 10].groupBy!((a, b) => abs(a-b) < 3); + assert(r3.equal!equal([ + [1, 3, 2], + [5, 4], + [9, 10] + ])); + } +} + +/// Showing usage with unary predicate: +/* FIXME: pure @safe nothrow*/ unittest +{ + import std.algorithm.comparison : equal; + + // Grouping by particular attribute of each element: + auto range = + [ + [1, 1], + [1, 1], + [1, 2], + [2, 2], + [2, 3], + [2, 3], + [3, 3] + ]; + + auto byX = groupBy!(a => a[0])(range); + auto expected1 = + [ + [[1, 1], [1, 1], [1, 2]], + [[2, 2], [2, 3], [2, 3]], + [[3, 3]] + ]; + foreach (e; byX) + { + assert(!expected1.empty); + assert(e.equal(expected1.front)); + expected1.popFront(); + } + + auto byY = groupBy!(a => a[1])(range); + auto expected2 = + [ + [[1, 1], [1, 1]], + [[1, 2], [2, 2]], + [[2, 3], [2, 3], [3, 3]] + ]; + foreach (e; byY) + { + assert(!expected2.empty); + assert(e.equal(expected2.front)); + expected2.popFront(); + } +} + +/*FIXME: pure @safe nothrow*/ unittest +{ + import std.algorithm.comparison : equal; + + struct Item { int x, y; } + + // Force R to have only an input range API with reference semantics, so + // that we're not unknowingly making use of array semantics outside of the + // range API. + class RefInputRange(R) + { + R data; + this(R _data) pure @safe nothrow { data = _data; } + @property bool empty() pure @safe nothrow { return data.empty; } + @property auto front() pure @safe nothrow { return data.front; } + void popFront() pure @safe nothrow { data.popFront(); } + } + auto refInputRange(R)(R range) { return new RefInputRange!R(range); } + + { + auto arr = [ Item(1,2), Item(1,3), Item(2,3) ]; + static assert(isForwardRange!(typeof(arr))); + + auto byX = groupBy!(a => a.x)(arr); + static assert(isForwardRange!(typeof(byX))); + + auto byX_subrange1 = byX.front.save; + auto byX_subrange2 = byX.front.save; + static assert(isForwardRange!(typeof(byX_subrange1))); + static assert(isForwardRange!(typeof(byX_subrange2))); + + byX.popFront(); + assert(byX_subrange1.equal([ Item(1,2), Item(1,3) ])); + byX_subrange1.popFront(); + assert(byX_subrange1.equal([ Item(1,3) ])); + assert(byX_subrange2.equal([ Item(1,2), Item(1,3) ])); + + auto byY = groupBy!(a => a.y)(arr); + static assert(isForwardRange!(typeof(byY))); + + auto byY2 = byY.save; + static assert(is(typeof(byY) == typeof(byY2))); + byY.popFront(); + assert(byY.front.equal([ Item(1,3), Item(2,3) ])); + assert(byY2.front.equal([ Item(1,2) ])); + } + + // Test non-forward input ranges. + { + auto range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); + auto byX = groupBy!(a => a.x)(range); + assert(byX.front.equal([ Item(1,1), Item(1,2) ])); + byX.popFront(); + assert(byX.front.equal([ Item(2,2) ])); + byX.popFront(); + assert(byX.empty); + assert(range.empty); + + range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); + auto byY = groupBy!(a => a.y)(range); + assert(byY.front.equal([ Item(1,1) ])); + byY.popFront(); + assert(byY.front.equal([ Item(1,2), Item(2,2) ])); + byY.popFront(); + assert(byY.empty); + assert(range.empty); + } +} + +// Issue 13595 +version(none) // This requires support for non-equivalence relations +unittest +{ + import std.algorithm.comparison : equal; + auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].groupBy!((x, y) => ((x*y) % 3) == 0); + assert(r.equal!equal([ + [1], + [2, 3, 4], + [5, 6, 7], + [8, 9] + ])); +} + +// Issue 13805 +unittest +{ + [""].map!((s) => s).groupBy!((x, y) => true); +} + +// joiner +/** +Lazily joins a range of ranges with a separator. The separator itself +is a range. If you do not provide a separator, then the ranges are +joined directly without anything in between them. + +Params: + r = An $(XREF2 range, isInputRange, input range) of input ranges to be + joined. + sep = A $(XREF2 range, isForwardRange, forward range) of element(s) to + serve as separators in the joined range. + +Returns: +An input range of elements in the joined range. This will be a forward range if +both outer and inner ranges of $(D RoR) are forward ranges; otherwise it will +be only an input range. + +See_also: +$(XREF range,chain), which chains a sequence of ranges with compatible elements +into a single range. + */ +auto joiner(RoR, Separator)(RoR r, Separator sep) +if (isInputRange!RoR && isInputRange!(ElementType!RoR) + && isForwardRange!Separator + && is(ElementType!Separator : ElementType!(ElementType!RoR))) +{ + static struct Result + { + private RoR _items; + private ElementType!RoR _current; + private Separator _sep, _currentSep; + + // This is a mixin instead of a function for the following reason (as + // explained by Kenji Hara): "This is necessary from 2.061. If a + // struct has a nested struct member, it must be directly initialized + // in its constructor to avoid leaving undefined state. If you change + // setItem to a function, the initialization of _current field is + // wrapped into private member function, then compiler could not detect + // that is correctly initialized while constructing. To avoid the + // compiler error check, string mixin is used." + private enum setItem = + q{ + if (!_items.empty) + { + // If we're exporting .save, we must not consume any of the + // subranges, since RoR.save does not guarantee that the states + // of the subranges are also saved. + static if (isForwardRange!RoR && + isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + } + }; + + private void useSeparator() + { + // Separator must always come after an item. + assert(_currentSep.empty && !_items.empty, + "joiner: internal error"); + _items.popFront(); + + // If there are no more items, we're done, since separators are not + // terminators. + if (_items.empty) return; + + if (_sep.empty) + { + // Advance to the next range in the + // input + while (_items.front.empty) + { + _items.popFront(); + if (_items.empty) return; + } + mixin(setItem); + } + else + { + _currentSep = _sep.save; + assert(!_currentSep.empty); + } + } + + private enum useItem = + q{ + // FIXME: this will crash if either _currentSep or _current are + // class objects, because .init is null when the ctor invokes this + // mixin. + //assert(_currentSep.empty && _current.empty, + // "joiner: internal error"); + + // Use the input + if (_items.empty) return; + mixin(setItem); + if (_current.empty) + { + // No data in the current item - toggle to use the separator + useSeparator(); + } + }; + + this(RoR items, Separator sep) + { + _items = items; + _sep = sep; + + //mixin(useItem); // _current should be initialized in place + if (_items.empty) + _current = _current.init; // set invalid state + else + { + // If we're exporting .save, we must not consume any of the + // subranges, since RoR.save does not guarantee that the states + // of the subranges are also saved. + static if (isForwardRange!RoR && + isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + + if (_current.empty) + { + // No data in the current item - toggle to use the separator + useSeparator(); + } + } + } + + @property auto empty() + { + return _items.empty; + } + + @property ElementType!(ElementType!RoR) front() + { + if (!_currentSep.empty) return _currentSep.front; + assert(!_current.empty); + return _current.front; + } + + void popFront() + { + assert(!_items.empty); + // Using separator? + if (!_currentSep.empty) + { + _currentSep.popFront(); + if (!_currentSep.empty) return; + mixin(useItem); + } + else + { + // we're using the range + _current.popFront(); + if (!_current.empty) return; + useSeparator(); + } + } + + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + { + @property auto save() + { + Result copy = this; + copy._items = _items.save; + copy._current = _current.save; + copy._sep = _sep.save; + copy._currentSep = _currentSep.save; + return copy; + } + } + } + return Result(r, sep); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + static assert(isInputRange!(typeof(joiner([""], "")))); + static assert(isForwardRange!(typeof(joiner([""], "")))); + assert(equal(joiner([""], "xyz"), ""), text(joiner([""], "xyz"))); + assert(equal(joiner(["", ""], "xyz"), "xyz"), text(joiner(["", ""], "xyz"))); + assert(equal(joiner(["", "abc"], "xyz"), "xyzabc")); + assert(equal(joiner(["abc", ""], "xyz"), "abcxyz")); + assert(equal(joiner(["abc", "def"], "xyz"), "abcxyzdef")); + assert(equal(joiner(["Mary", "has", "a", "little", "lamb"], "..."), + "Mary...has...a...little...lamb")); + assert(equal(joiner(["abc", "def"]), "abcdef")); +} + +unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives; + import std.range.interfaces; + // joiner() should work for non-forward ranges too. + auto r = inputRangeObject(["abc", "def"]); + assert (equal(joiner(r, "xyz"), "abcxyzdef")); +} + +unittest +{ + import std.algorithm.comparison : equal; + import std.range; + + // Related to issue 8061 + auto r = joiner([ + inputRangeObject("abc"), + inputRangeObject("def"), + ], "-*-"); + + assert(equal(r, "abc-*-def")); + + // Test case where separator is specified but is empty. + auto s = joiner([ + inputRangeObject("abc"), + inputRangeObject("def"), + ], ""); + + assert(equal(s, "abcdef")); + + // Test empty separator with some empty elements + auto t = joiner([ + inputRangeObject("abc"), + inputRangeObject(""), + inputRangeObject("def"), + inputRangeObject(""), + ], ""); + + assert(equal(t, "abcdef")); + + // Test empty elements with non-empty separator + auto u = joiner([ + inputRangeObject(""), + inputRangeObject("abc"), + inputRangeObject(""), + inputRangeObject("def"), + inputRangeObject(""), + ], "+-"); + + assert(equal(u, "+-abc+-+-def+-")); + + // Issue 13441: only(x) as separator + string[][] lines = [null]; + lines + .joiner(only("b")) + .array(); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + // Transience correctness test + struct TransientRange + { + @safe: + int[][] src; + int[] buf; + + this(int[][] _src) + { + src = _src; + buf.length = 100; + } + @property bool empty() { return src.empty; } + @property int[] front() + { + assert(src.front.length <= buf.length); + buf[0 .. src.front.length] = src.front[0..$]; + return buf[0 .. src.front.length]; + } + void popFront() { src.popFront(); } + } + + // Test embedded empty elements + auto tr1 = TransientRange([[], [1,2,3], [], [4]]); + assert(equal(joiner(tr1, [0]), [0,1,2,3,0,0,4])); + + // Test trailing empty elements + auto tr2 = TransientRange([[], [1,2,3], []]); + assert(equal(joiner(tr2, [0]), [0,1,2,3,0])); + + // Test no empty elements + auto tr3 = TransientRange([[1,2], [3,4]]); + assert(equal(joiner(tr3, [0,1]), [1,2,0,1,3,4])); + + // Test consecutive empty elements + auto tr4 = TransientRange([[1,2], [], [], [], [3,4]]); + assert(equal(joiner(tr4, [0,1]), [1,2,0,1,0,1,0,1,0,1,3,4])); + + // Test consecutive trailing empty elements + auto tr5 = TransientRange([[1,2], [3,4], [], []]); + assert(equal(joiner(tr5, [0,1]), [1,2,0,1,3,4,0,1,0,1])); +} + +/// Ditto +auto joiner(RoR)(RoR r) +if (isInputRange!RoR && isInputRange!(ElementType!RoR)) +{ + static struct Result + { + private: + RoR _items; + ElementType!RoR _current; + enum prepare = + q{ + // Skip over empty subranges. + if (_items.empty) return; + while (_items.front.empty) + { + _items.popFront(); + if (_items.empty) return; + } + // We cannot export .save method unless we ensure subranges are not + // consumed when a .save'd copy of ourselves is iterated over. So + // we need to .save each subrange we traverse. + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + }; + public: + this(RoR r) + { + _items = r; + //mixin(prepare); // _current should be initialized in place + + // Skip over empty subranges. + while (!_items.empty && _items.front.empty) + _items.popFront(); + + if (_items.empty) + _current = _current.init; // set invalid state + else + { + // We cannot export .save method unless we ensure subranges are not + // consumed when a .save'd copy of ourselves is iterated over. So + // we need to .save each subrange we traverse. + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + } + } + static if (isInfinite!RoR) + { + enum bool empty = false; + } + else + { + @property auto empty() + { + return _items.empty; + } + } + @property auto ref front() + { + assert(!empty); + return _current.front; + } + void popFront() + { + assert(!_current.empty); + _current.popFront(); + if (_current.empty) + { + assert(!_items.empty); + _items.popFront(); + mixin(prepare); + } + } + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + { + @property auto save() + { + Result copy = this; + copy._items = _items.save; + copy._current = _current.save; + return copy; + } + } + } + return Result(r); +} + +unittest +{ + import std.algorithm.comparison : equal; + import std.range.interfaces; + import std.range : repeat; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + static assert(isInputRange!(typeof(joiner([""])))); + static assert(isForwardRange!(typeof(joiner([""])))); + assert(equal(joiner([""]), "")); + assert(equal(joiner(["", ""]), "")); + assert(equal(joiner(["", "abc"]), "abc")); + assert(equal(joiner(["abc", ""]), "abc")); + assert(equal(joiner(["abc", "def"]), "abcdef")); + assert(equal(joiner(["Mary", "has", "a", "little", "lamb"]), + "Maryhasalittlelamb")); + assert(equal(joiner(std.range.repeat("abc", 3)), "abcabcabc")); + + // joiner allows in-place mutation! + auto a = [ [1, 2, 3], [42, 43] ]; + auto j = joiner(a); + j.front = 44; + assert(a == [ [44, 2, 3], [42, 43] ]); + + // bugzilla 8240 + assert(equal(joiner([inputRangeObject("")]), "")); + + // issue 8792 + auto b = [[1], [2], [3]]; + auto jb = joiner(b); + auto js = jb.save; + assert(equal(jb, js)); + + auto js2 = jb.save; + jb.popFront(); + assert(!equal(jb, js)); + assert(equal(js2, js)); + js.popFront(); + assert(equal(jb, js)); + assert(!equal(js2, js)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + struct TransientRange + { + @safe: + int[] _buf; + int[][] _values; + this(int[][] values) + { + _values = values; + _buf = new int[128]; + } + @property bool empty() + { + return _values.length == 0; + } + @property auto front() + { + foreach (i; 0 .. _values.front.length) + { + _buf[i] = _values[0][i]; + } + return _buf[0 .. _values.front.length]; + } + void popFront() + { + _values = _values[1 .. $]; + } + } + + auto rr = TransientRange([[1,2], [3,4,5], [], [6,7]]); + + // Can't use array() or equal() directly because they fail with transient + // .front. + int[] result; + foreach (c; rr.joiner()) { + result ~= c; + } + + assert(equal(result, [1,2,3,4,5,6,7])); +} + +@safe unittest +{ + import std.algorithm : algoFormat; // FIXME + import std.algorithm.comparison : equal; + + struct TransientRange + { + @safe: + dchar[] _buf; + dstring[] _values; + this(dstring[] values) + { + _buf.length = 128; + _values = values; + } + @property bool empty() + { + return _values.length == 0; + } + @property auto front() + { + foreach (i; 0 .. _values.front.length) + { + _buf[i] = _values[0][i]; + } + return _buf[0 .. _values.front.length]; + } + void popFront() + { + _values = _values[1 .. $]; + } + } + + auto rr = TransientRange(["abc"d, "12"d, "def"d, "34"d]); + + // Can't use array() or equal() directly because they fail with transient + // .front. + dchar[] result; + foreach (c; rr.joiner()) { + result ~= c; + } + + assert(equal(result, "abc12def34"d), + "Unexpected result: '%s'"d.algoFormat(result)); +} + +// Issue 8061 +unittest +{ + import std.range.interfaces; + import std.conv : to; + + auto r = joiner([inputRangeObject("ab"), inputRangeObject("cd")]); + assert(isForwardRange!(typeof(r))); + + auto str = to!string(r); + assert(str == "abcd"); +} + +/++ +Implements the homonym function (also known as $(D accumulate), $(D +compress), $(D inject), or $(D foldl)) present in various programming +languages of functional flavor. The call $(D reduce!(fun)(seed, +range)) first assigns $(D seed) to an internal variable $(D result), +also called the accumulator. Then, for each element $(D x) in $(D +range), $(D result = fun(result, x)) gets evaluated. Finally, $(D +result) is returned. The one-argument version $(D reduce!(fun)(range)) +works similarly, but it uses the first element of the range as the +seed (the range must be non-empty). + +Returns: + the accumulated $(D result) + +See_Also: + $(WEB en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) + + $(LREF sum) is similar to $(D reduce!((a, b) => a + b)) that offers + precise summing of floating point numbers. ++/ +template reduce(fun...) if (fun.length >= 1) +{ + import std.typetuple : staticMap; + + alias binfuns = staticMap!(binaryFun, fun); + static if (fun.length > 1) + import std.typecons : tuple, isTuple; + + /++ + No-seed version. The first element of $(D r) is used as the seed's value. + + For each function $(D f) in $(D fun), the corresponding + seed type $(D S) is $(D Unqual!(typeof(f(e, e)))), where $(D e) is an + element of $(D r): $(D ElementType!R) for ranges, + and $(D ForeachType!R) otherwise. + + Once S has been determined, then $(D S s = e;) and $(D s = f(s, e);) + must both be legal. + + If $(D r) is empty, an $(D Exception) is thrown. + +/ + auto reduce(R)(R r) + if (isIterable!R) + { + import std.exception : enforce; + alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + alias Args = staticMap!(ReduceSeedType!E, binfuns); + + static if (isInputRange!R) + { + enforce(!r.empty); + Args result = r.front; + r.popFront(); + return reduceImpl!false(r, result); + } + else + { + auto result = Args.init; + return reduceImpl!true(r, result); + } + } + + /++ + Seed version. The seed should be a single value if $(D fun) is a + single function. If $(D fun) is multiple functions, then $(D seed) + should be a $(XREF typecons,Tuple), with one field per function in $(D f). + + For convenience, if the seed is const, or has qualified fields, then + $(D reduce) will operate on an unqualified copy. If this happens + then the returned type will not perfectly match $(D S). + +/ + auto reduce(S, R)(S seed, R r) + if (isIterable!R) + { + static if (fun.length == 1) + return reducePreImpl(r, seed); + else + { + static assert(isTuple!S, algoFormat("Seed %s should be a Tuple", S.stringof)); + return reducePreImpl(r, seed.expand); + } + } + + private auto reducePreImpl(R, Args...)(R r, ref Args args) + { + alias Result = staticMap!(Unqual, Args); + static if (is(Result == Args)) + alias result = args; + else + Result result = args; + return reduceImpl!false(r, result); + } + + private auto reduceImpl(bool mustInitialize, R, Args...)(R r, ref Args args) + if (isIterable!R) + { + static assert(Args.length == fun.length, + algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length)); + alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + + static if (mustInitialize) bool initialized = false; + foreach (/+auto ref+/ E e; r) // @@@4707@@@ + { + foreach (i, f; binfuns) + static assert(is(typeof(args[i] = f(args[i], e))), + algoFormat("Incompatible function/seed/element: %s/%s/%s", fullyQualifiedName!f, Args[i].stringof, E.stringof)); + + static if (mustInitialize) if (initialized == false) + { + import std.conv : emplaceRef; + foreach (i, f; binfuns) + emplaceRef!(Args[i])(args[i], e); + initialized = true; + continue; + } + + foreach (i, f; binfuns) + args[i] = f(args[i], e); + } + static if (mustInitialize) if (!initialized) throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value."); + + static if (Args.length == 1) + return args[0]; + else + return tuple(args); + } +} + +//Helper for Reduce +private template ReduceSeedType(E) +{ + static template ReduceSeedType(alias fun) + { + E e = E.init; + static alias ReduceSeedType = Unqual!(typeof(fun(e, e))); + + //Check the Seed type is useable. + ReduceSeedType s = ReduceSeedType.init; + static assert(is(typeof({ReduceSeedType s = e;})) && is(typeof(s = fun(s, e))), + algoFormat("Unable to deduce an acceptable seed type for %s with element type %s.", fullyQualifiedName!fun, E.stringof)); + } +} + +/** +Many aggregate range operations turn out to be solved with $(D reduce) +quickly and easily. The example below illustrates $(D reduce)'s +remarkable power and flexibility. +*/ +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.math : approxEqual; + import std.range; + + int[] arr = [ 1, 2, 3, 4, 5 ]; + // Sum all elements + auto sum = reduce!((a,b) => a + b)(0, arr); + assert(sum == 15); + + // Sum again, using a string predicate with "a" and "b" + sum = reduce!"a + b"(0, arr); + assert(sum == 15); + + // Compute the maximum of all elements + auto largest = reduce!(max)(arr); + assert(largest == 5); + + // Max again, but with Uniform Function Call Syntax (UFCS) + largest = arr.reduce!(max); + assert(largest == 5); + + // Compute the number of odd elements + auto odds = reduce!((a,b) => a + (b & 1))(0, arr); + assert(odds == 3); + + // Compute the sum of squares + auto ssquares = reduce!((a,b) => a + b * b)(0, arr); + assert(ssquares == 55); + + // Chain multiple ranges into seed + int[] a = [ 3, 4 ]; + int[] b = [ 100 ]; + auto r = reduce!("a + b")(chain(a, b)); + assert(r == 107); + + // Mixing convertible types is fair game, too + double[] c = [ 2.5, 3.0 ]; + auto r1 = reduce!("a + b")(chain(a, b, c)); + assert(approxEqual(r1, 112.5)); + + // To minimize nesting of parentheses, Uniform Function Call Syntax can be used + auto r2 = chain(a, b, c).reduce!("a + b"); + assert(approxEqual(r2, 112.5)); +} + +/** +Sometimes it is very useful to compute multiple aggregates in one pass. +One advantage is that the computation is faster because the looping overhead +is shared. That's why $(D reduce) accepts multiple functions. +If two or more functions are passed, $(D reduce) returns a +$(XREF typecons, Tuple) object with one member per passed-in function. +The number of seeds must be correspondingly increased. +*/ +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.math : approxEqual, sqrt; + import std.typecons : tuple, Tuple; + + double[] a = [ 3.0, 4, 7, 11, 3, 2, 5 ]; + // Compute minimum and maximum in one pass + auto r = reduce!(min, max)(a); + // The type of r is Tuple!(int, int) + assert(approxEqual(r[0], 2)); // minimum + assert(approxEqual(r[1], 11)); // maximum + + // Compute sum and sum of squares in one pass + r = reduce!("a + b", "a + b * b")(tuple(0.0, 0.0), a); + assert(approxEqual(r[0], 35)); // sum + assert(approxEqual(r[1], 233)); // sum of squares + // Compute average and standard deviation from the above + auto avg = r[0] / a.length; + auto stdev = sqrt(r[1] / a.length - avg * avg); +} + +unittest +{ + import std.algorithm.comparison : max, min; + import std.exception : assertThrown; + import std.range; + import std.typecons : tuple, Tuple; + + double[] a = [ 3, 4 ]; + auto r = reduce!("a + b")(0.0, a); + assert(r == 7); + r = reduce!("a + b")(a); + assert(r == 7); + r = reduce!(min)(a); + assert(r == 3); + double[] b = [ 100 ]; + auto r1 = reduce!("a + b")(chain(a, b)); + assert(r1 == 107); + + // two funs + auto r2 = reduce!("a + b", "a - b")(tuple(0.0, 0.0), a); + assert(r2[0] == 7 && r2[1] == -7); + auto r3 = reduce!("a + b", "a - b")(a); + assert(r3[0] == 7 && r3[1] == -1); + + a = [ 1, 2, 3, 4, 5 ]; + // Stringize with commas + string rep = reduce!("a ~ `, ` ~ to!(string)(b)")("", a); + assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]"); + + // Test the opApply case. + static struct OpApply + { + bool actEmpty; + + int opApply(int delegate(ref int) dg) + { + int res; + if (actEmpty) return res; + + foreach (i; 0..100) + { + res = dg(i); + if (res) break; + } + return res; + } + } + + OpApply oa; + auto hundredSum = reduce!"a + b"(iota(100)); + assert(reduce!"a + b"(5, oa) == hundredSum + 5); + assert(reduce!"a + b"(oa) == hundredSum); + assert(reduce!("a + b", max)(oa) == tuple(hundredSum, 99)); + assert(reduce!("a + b", max)(tuple(5, 0), oa) == tuple(hundredSum + 5, 99)); + + // Test for throwing on empty range plus no seed. + assertThrown(reduce!"a + b"([1, 2][0..0])); + + oa.actEmpty = true; + assertThrown(reduce!"a + b"(oa)); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + const float a = 0.0; + const float[] b = [ 1.2, 3, 3.3 ]; + float[] c = [ 1.2, 3, 3.3 ]; + auto r = reduce!"a + b"(a, b); + r = reduce!"a + b"(a, c); +} + +@safe unittest +{ + // Issue #10408 - Two-function reduce of a const array. + import std.algorithm.comparison : max, min; + import std.typecons : tuple, Tuple; + + const numbers = [10, 30, 20]; + immutable m = reduce!(min)(numbers); + assert(m == 10); + immutable minmax = reduce!(min, max)(numbers); + assert(minmax == tuple(10, 30)); +} + +@safe unittest +{ + //10709 + import std.typecons : tuple, Tuple; + + enum foo = "a + 0.5 * b"; + auto r = [0, 1, 2, 3]; + auto r1 = reduce!foo(r); + auto r2 = reduce!(foo, foo)(r); + assert(r1 == 3); + assert(r2 == tuple(3, 3)); +} + +unittest +{ + int i = 0; + static struct OpApply + { + int opApply(int delegate(ref int) dg) + { + int[] a = [1, 2, 3]; + + int res = 0; + foreach (ref e; a) + { + res = dg(e); + if (res) break; + } + return res; + } + } + //test CTFE and functions with context + int fun(int a, int b){return a + b + 1;} + auto foo() + { + import std.algorithm.comparison : max; + import std.typecons : tuple, Tuple; + + auto a = reduce!(fun)([1, 2, 3]); + auto b = reduce!(fun, fun)([1, 2, 3]); + auto c = reduce!(fun)(0, [1, 2, 3]); + auto d = reduce!(fun, fun)(tuple(0, 0), [1, 2, 3]); + auto e = reduce!(fun)(0, OpApply()); + auto f = reduce!(fun, fun)(tuple(0, 0), OpApply()); + + return max(a, b.expand, c, d.expand); + } + auto a = foo(); + enum b = foo(); +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.typecons : tuple, Tuple; + + //http://forum.dlang.org/thread/oghtttkopzjshsuflelk@forum.dlang.org + //Seed is tuple of const. + static auto minmaxElement(alias F = min, alias G = max, R)(in R range) + @safe pure nothrow if (isInputRange!R) + { + return reduce!(F, G)(tuple(ElementType!R.max, + ElementType!R.min), range); + } + assert(minmaxElement([1, 2, 3])== tuple(1, 3)); +} + +@safe unittest //12569 +{ + import std.algorithm.comparison : max, min; + import std.typecons: tuple; + dchar c = 'a'; + reduce!(min, max)(tuple(c, c), "hello"); // OK + static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello")))); + static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello")))); + + + //"Seed dchar should be a Tuple" + static assert(!is(typeof(reduce!(min, max)(c, "hello")))); + //"Seed (dchar) does not have the correct amount of fields (should be 2)" + static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello")))); + //"Seed (dchar, dchar, dchar) does not have the correct amount of fields (should be 2)" + static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello")))); + //"Incompatable function/seed/element: all(alias pred = "a")/int/dchar" + static assert(!is(typeof(reduce!all(1, "hello")))); + static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello")))); +} + +@safe unittest //13304 +{ + int[] data; + static assert(is(typeof(reduce!((a, b)=>a+b)(data)))); +} + +// splitter +/** +Lazily splits a range using an element as a separator. This can be used with +any narrow string type or sliceable range type, but is most popular with string +types. + +Two adjacent separators are considered to surround an empty element in +the split range. Use $(D filter!(a => !a.empty)) on the result to compress +empty elements. + +If the empty range is given, the result is a range with one empty +element. If a range with one separator is given, the result is a range +with two empty elements. + +If splitting a string on whitespace and token compression is desired, +consider using $(D splitter) without specifying a separator (see fourth overload +below). + +Params: + pred = The predicate for comparing each element with the separator, + defaulting to $(D "a == b"). + r = The $(XREF2 range, isInputRange, input range) to be split. Must support + slicing and $(D .length). + s = The element to be treated as the separator between range segments to be + split. + +Constraints: + The predicate $(D pred) needs to accept an element of $(D r) and the + separator $(D s). + +Returns: + An input range of the subranges of elements between separators. If $(D r) + is a forward range or bidirectional range, the returned range will be + likewise. + +See_Also: + $(XREF regex, _splitter) for a version that splits using a regular +expression defined separator. +*/ +auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s) +if (is(typeof(binaryFun!pred(r.front, s)) : bool) + && ((hasSlicing!Range && hasLength!Range) || isNarrowString!Range)) +{ + import std.algorithm.searching : find; + import std.conv : unsigned; + + static struct Result + { + private: + Range _input; + Separator _separator; + // Do we need hasLength!Range? popFront uses _input.length... + alias IndexType = typeof(unsigned(_input.length)); + enum IndexType _unComputed = IndexType.max - 1, _atEnd = IndexType.max; + IndexType _frontLength = _unComputed; + IndexType _backLength = _unComputed; + + static if (isNarrowString!Range) + { + size_t _separatorLength; + } + else + { + enum _separatorLength = 1; + } + + static if (isBidirectionalRange!Range) + { + static IndexType lastIndexOf(Range haystack, Separator needle) + { + import std.range : retro; + auto r = haystack.retro().find!pred(needle); + return r.retro().length - 1; + } + } + + public: + this(Range input, Separator separator) + { + _input = input; + _separator = separator; + + static if (isNarrowString!Range) + { + import std.utf : codeLength; + + _separatorLength = codeLength!(ElementEncodingType!Range)(separator); + } + if (_input.empty) + _frontLength = _atEnd; + } + + static if (isInfinite!Range) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + return _frontLength == _atEnd; + } + } + + @property Range front() + { + assert(!empty); + if (_frontLength == _unComputed) + { + auto r = _input.find!pred(_separator); + _frontLength = _input.length - r.length; + } + return _input[0 .. _frontLength]; + } + + void popFront() + { + assert(!empty); + if (_frontLength == _unComputed) + { + front; + } + assert(_frontLength <= _input.length); + if (_frontLength == _input.length) + { + // no more input and need to fetch => done + _frontLength = _atEnd; + + // Probably don't need this, but just for consistency: + _backLength = _atEnd; + } + else + { + _input = _input[_frontLength + _separatorLength .. _input.length]; + _frontLength = _unComputed; + } + } + + static if (isForwardRange!Range) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + + static if (isBidirectionalRange!Range) + { + @property Range back() + { + assert(!empty); + if (_backLength == _unComputed) + { + immutable lastIndex = lastIndexOf(_input, _separator); + if (lastIndex == -1) + { + _backLength = _input.length; + } + else + { + _backLength = _input.length - lastIndex - 1; + } + } + return _input[_input.length - _backLength .. _input.length]; + } + + void popBack() + { + assert(!empty); + if (_backLength == _unComputed) + { + // evaluate back to make sure it's computed + back; + } + assert(_backLength <= _input.length); + if (_backLength == _input.length) + { + // no more input and need to fetch => done + _frontLength = _atEnd; + _backLength = _atEnd; + } + else + { + _input = _input[0 .. _input.length - _backLength - _separatorLength]; + _backLength = _unComputed; + } + } + } + } + + return Result(r, s); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; + assert(equal(splitter(a, 0), w)); + a = [ 0 ]; + assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ])); + a = [ 0, 1 ]; + assert(equal(splitter(a, 0), [ [], [1] ])); + w = [ [0], [1], [2] ]; + assert(equal(splitter!"a.front == b"(w, 1), [ [[0]], [[2]] ])); +} + +@safe unittest +{ + import std.internal.test.dummyrange; + import std.algorithm; + import std.array : array; + import std.range : retro; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ])); + assert(equal(splitter("žlutoučkýřkůň", 'ř'), [ "žlutoučký", "kůň" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; + static assert(isForwardRange!(typeof(splitter(a, 0)))); + + // foreach (x; splitter(a, 0)) { + // writeln("[", x, "]"); + // } + assert(equal(splitter(a, 0), w)); + a = null; + assert(equal(splitter(a, 0), (int[][]).init)); + a = [ 0 ]; + assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ][])); + a = [ 0, 1 ]; + assert(equal(splitter(a, 0), [ [], [1] ][])); + + // Thoroughly exercise the bidirectional stuff. + auto str = "abc abcd abcde ab abcdefg abcdefghij ab ac ar an at ada"; + assert(equal( + retro(splitter(str, 'a')), + retro(array(splitter(str, 'a'))) + )); + + // Test interleaving front and back. + auto split = splitter(str, 'a'); + assert(split.front == ""); + assert(split.back == ""); + split.popBack(); + assert(split.back == "d"); + split.popFront(); + assert(split.front == "bc "); + assert(split.back == "d"); + split.popFront(); + split.popBack(); + assert(split.back == "t "); + split.popBack(); + split.popBack(); + split.popFront(); + split.popFront(); + assert(split.front == "b "); + assert(split.back == "r "); + + foreach (DummyType; AllDummyRanges) { // Bug 4408 + static if (isRandomAccessRange!DummyType) { + static assert(isBidirectionalRange!DummyType); + DummyType d; + auto s = splitter(d, 5); + assert(equal(s.front, [1,2,3,4])); + assert(equal(s.back, [6,7,8,9,10])); + + auto s2 = splitter(d, [4, 5]); + assert(equal(s2.front, [1,2,3])); + } + } +} +@safe unittest +{ + import std.algorithm; + import std.range; + auto L = retro(iota(1L, 10L)); + auto s = splitter(L, 5L); + assert(equal(s.front, [9L, 8L, 7L, 6L])); + s.popFront(); + assert(equal(s.front, [4L, 3L, 2L, 1L])); + s.popFront(); + assert(s.empty); +} + +/** +Similar to the previous overload of $(D splitter), except this one uses another +range as a separator. This can be used with any narrow string type or sliceable +range type, but is most popular with string types. + +Two adjacent separators are considered to surround an empty element in +the split range. Use $(D filter!(a => !a.empty)) on the result to compress +empty elements. + +Params: + pred = The predicate for comparing each element with the separator, + defaulting to $(D "a == b"). + r = The $(XREF2 range, isInputRange, input range) to be split. + s = The $(XREF2 range, isForwardRange, forward range) to be treated as the + separator between segments of $(D r) to be split. + +Constraints: + The predicate $(D pred) needs to accept an element of $(D r) and an + element of $(D s). + +Returns: + An input range of the subranges of elements between separators. If $(D r) + is a forward range or bidirectional range, the returned range will be + likewise. + +See_Also: $(XREF regex, _splitter) for a version that splits using a regular +expression defined separator. + */ +auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s) +if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) + && (hasSlicing!Range || isNarrowString!Range) + && isForwardRange!Separator + && (hasLength!Separator || isNarrowString!Separator)) +{ + import std.algorithm.searching : find; + import std.conv : unsigned; + + static struct Result + { + private: + Range _input; + Separator _separator; + alias RIndexType = typeof(unsigned(_input.length)); + // _frontLength == size_t.max means empty + RIndexType _frontLength = RIndexType.max; + static if (isBidirectionalRange!Range) + RIndexType _backLength = RIndexType.max; + + @property auto separatorLength() { return _separator.length; } + + void ensureFrontLength() + { + if (_frontLength != _frontLength.max) return; + assert(!_input.empty); + // compute front length + _frontLength = (_separator.empty) ? 1 : + _input.length - find!pred(_input, _separator).length; + static if (isBidirectionalRange!Range) + if (_frontLength == _input.length) _backLength = _frontLength; + } + + void ensureBackLength() + { + static if (isBidirectionalRange!Range) + if (_backLength != _backLength.max) return; + assert(!_input.empty); + // compute back length + static if (isBidirectionalRange!Range && isBidirectionalRange!Separator) + { + import std.range : retro; + _backLength = _input.length - + find!pred(retro(_input), retro(_separator)).source.length; + } + } + + public: + this(Range input, Separator separator) + { + _input = input; + _separator = separator; + } + + @property Range front() + { + assert(!empty); + ensureFrontLength(); + return _input[0 .. _frontLength]; + } + + static if (isInfinite!Range) + { + enum bool empty = false; // Propagate infiniteness + } + else + { + @property bool empty() + { + return _frontLength == RIndexType.max && _input.empty; + } + } + + void popFront() + { + assert(!empty); + ensureFrontLength(); + if (_frontLength == _input.length) + { + // done, there's no separator in sight + _input = _input[_frontLength .. _frontLength]; + _frontLength = _frontLength.max; + static if (isBidirectionalRange!Range) + _backLength = _backLength.max; + return; + } + if (_frontLength + separatorLength == _input.length) + { + // Special case: popping the first-to-last item; there is + // an empty item right after this. + _input = _input[_input.length .. _input.length]; + _frontLength = 0; + static if (isBidirectionalRange!Range) + _backLength = 0; + return; + } + // Normal case, pop one item and the separator, get ready for + // reading the next item + _input = _input[_frontLength + separatorLength .. _input.length]; + // mark _frontLength as uninitialized + _frontLength = _frontLength.max; + } + + static if (isForwardRange!Range) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + + // Bidirectional functionality as suggested by Brad Roberts. + static if (isBidirectionalRange!Range && isBidirectionalRange!Separator) + { + //Deprecated. It will be removed in December 2015 + deprecated("splitter!(Range, Range) cannot be iterated backwards (due to separator overlap).") + @property Range back() + { + ensureBackLength(); + return _input[_input.length - _backLength .. _input.length]; + } + + //Deprecated. It will be removed in December 2015 + deprecated("splitter!(Range, Range) cannot be iterated backwards (due to separator overlap).") + void popBack() + { + ensureBackLength(); + if (_backLength == _input.length) + { + // done + _input = _input[0 .. 0]; + _frontLength = _frontLength.max; + _backLength = _backLength.max; + return; + } + if (_backLength + separatorLength == _input.length) + { + // Special case: popping the first-to-first item; there is + // an empty item right before this. Leave the separator in. + _input = _input[0 .. 0]; + _frontLength = 0; + _backLength = 0; + return; + } + // Normal case + _input = _input[0 .. _input.length - _backLength - separatorLength]; + _backLength = _backLength.max; + } + } + } + + return Result(r, s); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert(equal(splitter("hello world", " "), [ "hello", "world" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [3, 0, 4, 5, 0] ]; + assert(equal(splitter(a, [0, 0]), w)); + a = [ 0, 0 ]; + assert(equal(splitter(a, [0, 0]), [ (int[]).init, (int[]).init ])); + a = [ 0, 0, 1 ]; + assert(equal(splitter(a, [0, 0]), [ [], [1] ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Tuple; + + alias C = Tuple!(int, "x", int, "y"); + auto a = [C(1,0), C(2,0), C(3,1), C(4,0)]; + assert(equal(splitter!"a.x == b"(a, [2, 3]), [ [C(1,0)], [C(4,0)] ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + import std.array : split; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto s = ",abc, de, fg,hi,"; + auto sp0 = splitter(s, ','); + // //foreach (e; sp0) writeln("[", e, "]"); + assert(equal(sp0, ["", "abc", " de", " fg", "hi", ""][])); + + auto s1 = ", abc, de, fg, hi, "; + auto sp1 = splitter(s1, ", "); + //foreach (e; sp1) writeln("[", e, "]"); + assert(equal(sp1, ["", "abc", "de", " fg", "hi", ""][])); + static assert(isForwardRange!(typeof(sp1))); + + int[] a = [ 1, 2, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [3], [4, 5], [] ]; + uint i; + foreach (e; splitter(a, 0)) + { + assert(i < w.length); + assert(e == w[i++]); + } + assert(i == w.length); + // // Now go back + // auto s2 = splitter(a, 0); + + // foreach (e; retro(s2)) + // { + // assert(i > 0); + // assert(equal(e, w[--i]), text(e)); + // } + // assert(i == 0); + + wstring names = ",peter,paul,jerry,"; + auto words = split(names, ","); + assert(walkLength(words) == 5, text(walkLength(words))); +} + +@safe unittest +{ + int[][] a = [ [1], [2], [0], [3], [0], [4], [5], [0] ]; + int[][][] w = [ [[1], [2]], [[3]], [[4], [5]], [] ]; + uint i; + foreach (e; splitter!"a.front == 0"(a, 0)) + { + assert(i < w.length); + assert(e == w[i++]); + } + assert(i == w.length); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto s6 = ","; + auto sp6 = splitter(s6, ','); + foreach (e; sp6) + { + //writeln("{", e, "}"); + } + assert(equal(sp6, ["", ""][])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + // Issue 10773 + auto s = splitter("abc", ""); + assert(s.equal(["a", "b", "c"])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + // Test by-reference separator + class RefSep { + @safe: + string _impl; + this(string s) { _impl = s; } + @property empty() { return _impl.empty; } + @property auto front() { return _impl.front; } + void popFront() { _impl = _impl[1..$]; } + @property RefSep save() { return new RefSep(_impl); } + @property auto length() { return _impl.length; } + } + auto sep = new RefSep("->"); + auto data = "i->am->pointing"; + auto words = splitter(data, sep); + assert(words.equal([ "i", "am", "pointing" ])); +} + +/** + +Similar to the previous overload of $(D splitter), except this one does not use a separator. +Instead, the predicate is an unary function on the input range's element type. + +Two adjacent separators are considered to surround an empty element in +the split range. Use $(D filter!(a => !a.empty)) on the result to compress +empty elements. + +Params: + isTerminator = The predicate for deciding where to split the range. + input = The $(XREF2 range, isInputRange, input range) to be split. + +Constraints: + The predicate $(D isTerminator) needs to accept an element of $(D input). + +Returns: + An input range of the subranges of elements between separators. If $(D input) + is a forward range or bidirectional range, the returned range will be + likewise. + +See_Also: $(XREF regex, _splitter) for a version that splits using a regular +expression defined separator. + */ +auto splitter(alias isTerminator, Range)(Range input) +if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(input.front)))) +{ + return SplitterResult!(unaryFun!isTerminator, Range)(input); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert(equal(splitter!"a == ' '"("hello world"), [ "hello", "", "world" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; + assert(equal(splitter!"a == 0"(a), w)); + a = [ 0 ]; + assert(equal(splitter!"a == 0"(a), [ (int[]).init, (int[]).init ])); + a = [ 0, 1 ]; + assert(equal(splitter!"a == 0"(a), [ [], [1] ])); + w = [ [0], [1], [2] ]; + assert(equal(splitter!"a.front == 1"(w), [ [[0]], [[2]] ])); +} + +private struct SplitterResult(alias isTerminator, Range) +{ + import std.algorithm.searching : find; + enum fullSlicing = (hasLength!Range && hasSlicing!Range) || isSomeString!Range; + + private Range _input; + private size_t _end = 0; + static if(!fullSlicing) + private Range _next; + + private void findTerminator() + { + static if (fullSlicing) + { + auto r = find!isTerminator(_input.save); + _end = _input.length - r.length; + } + else + for ( _end = 0; !_next.empty ; _next.popFront) + { + if (isTerminator(_next.front)) + break; + ++_end; + } + } + + this(Range input) + { + _input = input; + static if(!fullSlicing) + _next = _input.save; + + if (!_input.empty) + findTerminator(); + else + _end = size_t.max; + } + + static if (isInfinite!Range) + { + enum bool empty = false; // Propagate infiniteness. + } + else + { + @property bool empty() + { + return _end == size_t.max; + } + } + + @property auto front() + { + version(assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + static if (fullSlicing) + return _input[0 .. _end]; + else + { + import std.range : takeExactly; + return _input.takeExactly(_end); + } + } + + void popFront() + { + version(assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + + static if (fullSlicing) + { + _input = _input[_end .. _input.length]; + if (_input.empty) + { + _end = size_t.max; + return; + } + _input.popFront(); + } + else + { + if (_next.empty) + { + _input = _next; + _end = size_t.max; + return; + } + _next.popFront(); + _input = _next.save; + } + findTerminator(); + } + + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + static if (!fullSlicing) + ret._next = _next.save; + return ret; + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + + auto L = iota(1L, 10L); + auto s = splitter(L, [5L, 6L]); + assert(equal(s.front, [1L, 2L, 3L, 4L])); + s.popFront(); + assert(equal(s.front, [7L, 8L, 9L])); + s.popFront(); + assert(s.empty); +} + +@safe unittest +{ + import std.algorithm : algoFormat; // FIXME + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + void compare(string sentence, string[] witness) + { + auto r = splitter!"a == ' '"(sentence); + assert(equal(r.save, witness), algoFormat("got: %(%s, %) expected: %(%s, %)", r, witness)); + } + + compare(" Mary has a little lamb. ", + ["", "Mary", "", "has", "a", "little", "lamb.", "", "", ""]); + compare("Mary has a little lamb. ", + ["Mary", "", "has", "a", "little", "lamb.", "", "", ""]); + compare("Mary has a little lamb.", + ["Mary", "", "has", "a", "little", "lamb."]); + compare("", (string[]).init); + compare(" ", ["", ""]); + + static assert(isForwardRange!(typeof(splitter!"a == ' '"("ABC")))); + + foreach (DummyType; AllDummyRanges) + { + static if (isRandomAccessRange!DummyType) + { + auto rangeSplit = splitter!"a == 5"(DummyType.init); + assert(equal(rangeSplit.front, [1,2,3,4])); + rangeSplit.popFront(); + assert(equal(rangeSplit.front, [6,7,8,9,10])); + } + } +} + +@safe unittest +{ + import std.algorithm : algoFormat; // FIXME + import std.algorithm.comparison : equal; + import std.range; + + struct Entry + { + int low; + int high; + int[][] result; + } + Entry[] entries = [ + Entry(0, 0, []), + Entry(0, 1, [[0]]), + Entry(1, 2, [[], []]), + Entry(2, 7, [[2], [4], [6]]), + Entry(1, 8, [[], [2], [4], [6], []]), + ]; + foreach ( entry ; entries ) + { + auto a = iota(entry.low, entry.high).filter!"true"(); + auto b = splitter!"a%2"(a); + assert(equal!equal(b.save, entry.result), algoFormat("got: %(%s, %) expected: %(%s, %)", b, entry.result)); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.uni : isWhite; + + //@@@6791@@@ + assert(equal(splitter("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"])); + assert(equal(splitter!(std.uni.isWhite)("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"])); + assert(equal(splitter!"a=='本'"("日本語"), ["日", "語"])); +} + +/++ +Lazily splits the string $(D s) into words, using whitespace as the delimiter. + +This function is string specific and, contrary to +$(D splitter!(std.uni.isWhite)), runs of whitespace will be merged together +(no empty tokens will be produced). + +Params: + s = The string to be split. + +Returns: + An $(XREF2 range, isInputRange, input range) of slices of the original + string split by whitespace. + +/ +auto splitter(C)(C[] s) +if (isSomeChar!C) +{ + import std.algorithm.searching : find; + static struct Result + { + private: + import core.exception; + C[] _s; + size_t _frontLength; + + void getFirst() pure @safe + { + import std.uni : isWhite; + + auto r = find!(isWhite)(_s); + _frontLength = _s.length - r.length; + } + + public: + this(C[] s) pure @safe + { + import std.string : strip; + _s = s.strip(); + getFirst(); + } + + @property C[] front() pure @safe + { + version(assert) if (empty) throw new RangeError(); + return _s[0 .. _frontLength]; + } + + void popFront() pure @safe + { + import std.string : stripLeft; + version(assert) if (empty) throw new RangeError(); + _s = _s[_frontLength .. $].stripLeft(); + getFirst(); + } + + @property bool empty() const @safe pure nothrow + { + return _s.empty; + } + + @property inout(Result) save() inout @safe pure nothrow + { + return this; + } + } + return Result(s); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + auto a = " a bcd ef gh "; + assert(equal(splitter(a), ["a", "bcd", "ef", "gh"][])); +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.typetuple : TypeTuple; + foreach(S; TypeTuple!(string, wstring, dstring)) + { + import std.conv : to; + S a = " a bcd ef gh "; + assert(equal(splitter(a), [to!S("a"), to!S("bcd"), to!S("ef"), to!S("gh")])); + a = ""; + assert(splitter(a).empty); + } + + immutable string s = " a bcd ef gh "; + assert(equal(splitter(s), ["a", "bcd", "ef", "gh"][])); +} + +@safe unittest +{ + import std.conv : to; + import std.string : strip; + + // TDPL example, page 8 + uint[string] dictionary; + char[][3] lines; + lines[0] = "line one".dup; + lines[1] = "line \ttwo".dup; + lines[2] = "yah last line\ryah".dup; + foreach (line; lines) { + foreach (word; splitter(strip(line))) { + if (word in dictionary) continue; // Nothing to do + auto newID = dictionary.length; + dictionary[to!string(word)] = cast(uint)newID; + } + } + assert(dictionary.length == 5); + assert(dictionary["line"]== 0); + assert(dictionary["one"]== 1); + assert(dictionary["two"]== 2); + assert(dictionary["yah"]== 3); + assert(dictionary["last"]== 4); +} + +@safe unittest +{ + import std.algorithm : algoFormat; // FIXME + import std.algorithm.comparison : equal; + import std.conv : text; + import std.array : split; + + // Check consistency: + // All flavors of split should produce the same results + foreach (input; [(int[]).init, + [0], + [0, 1, 0], + [1, 1, 0, 0, 1, 1], + ]) + { + foreach (s; [0, 1]) + { + auto result = split(input, s); + + assert(equal(result, split(input, [s])), algoFormat(`"[%(%s,%)]"`, split(input, [s]))); + //assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, split!((a) => a == s)(input)), text(split!((a) => a == s)(input))); + + //assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"()))); + + assert(equal(result, splitter(input, s))); + assert(equal(result, splitter(input, [s]))); + //assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, splitter!((a) => a == s)(input))); + + //assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"()))); + } + } + foreach (input; [string.init, + " ", + " hello ", + "hello hello", + " hello what heck this ? " + ]) + { + foreach (s; [' ', 'h']) + { + auto result = split(input, s); + + assert(equal(result, split(input, [s]))); + //assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, split!((a) => a == s)(input))); + + //assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"()))); + + assert(equal(result, splitter(input, s))); + assert(equal(result, splitter(input, [s]))); + //assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, splitter!((a) => a == s)(input))); + + //assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"()))); + } + } +} + +// sum +/** +Sums elements of $(D r), which must be a finite $(XREF2 range, isInputRange, input range). Although +conceptually $(D sum(r)) is equivalent to $(LREF reduce)!((a, b) => a + +b)(0, r), $(D sum) uses specialized algorithms to maximize accuracy, +as follows. + +$(UL +$(LI If $(D $(XREF range, ElementType)!R) is a floating-point type and $(D R) is a +$(XREF2 range, isRandomAccessRange, random-access range) with length and slicing, then $(D sum) uses the +$(WEB en.wikipedia.org/wiki/Pairwise_summation, pairwise summation) +algorithm.) +$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a +finite input range (but not a random-access range with slicing), then +$(D sum) uses the $(WEB en.wikipedia.org/wiki/Kahan_summation, +Kahan summation) algorithm.) +$(LI In all other cases, a simple element by element addition is done.) +) + +For floating point inputs, calculations are made in $(LINK2 ../type.html, $(D real)) +precision for $(D real) inputs and in $(D double) precision otherwise +(Note this is a special case that deviates from $(D reduce)'s behavior, +which would have kept $(D float) precision for a $(D float) range). +For all other types, the calculations are done in the same type obtained +from from adding two elements of the range, which may be a different +type from the elements themselves (for example, in case of $(LINK2 ../type.html#integer-promotions, integral promotion)). + +A seed may be passed to $(D sum). Not only will this seed be used as an initial +value, but its type will override all the above, and determine the algorithm +and precision used for sumation. + +Note that these specialized summing algorithms execute more primitive operations +than vanilla summation. Therefore, if in certain cases maximum speed is required +at expense of precision, one can use $(D reduce!((a, b) => a + b)(0, r)), which +is not specialized for summation. + +Returns: + The sum of all the elements in the range r. + */ +auto sum(R)(R r) +if (isInputRange!R && !isInfinite!R && is(typeof(r.front + r.front))) +{ + alias E = Unqual!(ElementType!R); + static if (isFloatingPoint!E) + alias Seed = typeof(E.init + 0.0); //biggest of double/real + else + alias Seed = typeof(r.front + r.front); + return sum(r, Unqual!Seed(0)); +} +/// ditto +auto sum(R, E)(R r, E seed) +if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front))) +{ + static if (isFloatingPoint!E) + { + static if (hasLength!R && hasSlicing!R) + return seed + sumPairwise!E(r); + else + return sumKahan!E(seed, r); + } + else + { + return reduce!"a + b"(seed, r); + } +} + +// Pairwise summation http://en.wikipedia.org/wiki/Pairwise_summation +private auto sumPairwise(Result, R)(R r) +{ + static assert (isFloatingPoint!Result); + switch (r.length) + { + case 0: return cast(Result) 0; + case 1: return cast(Result) r.front; + case 2: return cast(Result) r[0] + cast(Result) r[1]; + default: return sumPairwise!Result(r[0 .. $ / 2]) + sumPairwise!Result(r[$ / 2 .. $]); + } +} + +// Kahan algo http://en.wikipedia.org/wiki/Kahan_summation_algorithm +private auto sumKahan(Result, R)(Result result, R r) +{ + static assert (isFloatingPoint!Result && isMutable!Result); + Result c = 0; + for (; !r.empty; r.popFront()) + { + auto y = r.front - c; + auto t = result + y; + c = (t - result) - y; + result = t; + } + return result; +} + +/// Ditto +@safe pure nothrow unittest +{ + import std.range; + + //simple integral sumation + assert(sum([ 1, 2, 3, 4]) == 10); + + //with integral promotion + assert(sum([false, true, true, false, true]) == 3); + assert(sum(ubyte.max.repeat(100)) == 25500); + + //The result may overflow + assert(uint.max.repeat(3).sum() == 4294967293U ); + //But a seed can be used to change the sumation primitive + assert(uint.max.repeat(3).sum(ulong.init) == 12884901885UL); + + //Floating point sumation + assert(sum([1.0, 2.0, 3.0, 4.0]) == 10); + + //Floating point operations have double precision minimum + static assert(is(typeof(sum([1F, 2F, 3F, 4F])) == double)); + assert(sum([1F, 2, 3, 4]) == 10); + + //Force pair-wise floating point sumation on large integers + import std.math : approxEqual; + assert(iota(ulong.max / 2, ulong.max / 2 + 4096).sum(0.0) + .approxEqual((ulong.max / 2) * 4096.0 + 4096^^2 / 2)); +} + +@safe pure nothrow unittest +{ + static assert(is(typeof(sum([cast( byte)1])) == int)); + static assert(is(typeof(sum([cast(ubyte)1])) == int)); + static assert(is(typeof(sum([ 1, 2, 3, 4])) == int)); + static assert(is(typeof(sum([ 1U, 2U, 3U, 4U])) == uint)); + static assert(is(typeof(sum([ 1L, 2L, 3L, 4L])) == long)); + static assert(is(typeof(sum([1UL, 2UL, 3UL, 4UL])) == ulong)); + + int[] empty; + assert(sum(empty) == 0); + assert(sum([42]) == 42); + assert(sum([42, 43]) == 42 + 43); + assert(sum([42, 43, 44]) == 42 + 43 + 44); + assert(sum([42, 43, 44, 45]) == 42 + 43 + 44 + 45); +} + +@safe pure nothrow unittest +{ + static assert(is(typeof(sum([1.0, 2.0, 3.0, 4.0])) == double)); + static assert(is(typeof(sum([ 1F, 2F, 3F, 4F])) == double)); + const(float[]) a = [1F, 2F, 3F, 4F]; + static assert(is(typeof(sum(a)) == double)); + const(float)[] b = [1F, 2F, 3F, 4F]; + static assert(is(typeof(sum(a)) == double)); + + double[] empty; + assert(sum(empty) == 0); + assert(sum([42.]) == 42); + assert(sum([42., 43.]) == 42 + 43); + assert(sum([42., 43., 44.]) == 42 + 43 + 44); + assert(sum([42., 43., 44., 45.5]) == 42 + 43 + 44 + 45.5); +} + +@safe pure nothrow unittest +{ + import std.container; + static assert(is(typeof(sum(SList!float()[])) == double)); + static assert(is(typeof(sum(SList!double()[])) == double)); + static assert(is(typeof(sum(SList!real()[])) == real)); + + assert(sum(SList!double()[]) == 0); + assert(sum(SList!double(1)[]) == 1); + assert(sum(SList!double(1, 2)[]) == 1 + 2); + assert(sum(SList!double(1, 2, 3)[]) == 1 + 2 + 3); + assert(sum(SList!double(1, 2, 3, 4)[]) == 10); +} + +@safe pure nothrow unittest // 12434 +{ + immutable a = [10, 20]; + auto s1 = sum(a); // Error + auto s2 = a.map!(x => x).sum; // Error +} + +unittest +{ + import std.bigint; + import std.range; + + immutable BigInt[] a = BigInt("1_000_000_000_000_000_000").repeat(10).array(); + immutable ulong[] b = (ulong.max/2).repeat(10).array(); + auto sa = a.sum(); + auto sb = b.sum(BigInt(0)); //reduce ulongs into bigint + assert(sa == BigInt("10_000_000_000_000_000_000")); + assert(sb == (BigInt(ulong.max/2) * 10)); +} + +// uniq +/** +Lazily iterates unique consecutive elements of the given range (functionality +akin to the $(WEB wikipedia.org/wiki/_Uniq, _uniq) system +utility). Equivalence of elements is assessed by using the predicate +$(D pred), by default $(D "a == b"). If the given range is +bidirectional, $(D uniq) also yields a bidirectional range. + +Params: + pred = Predicate for determining equivalence between range elements. + r = An $(XREF2 range, isInputRange, input range) of elements to filter. + +Returns: + An $(XREF2 range, isInputRange, input range) of consecutively unique + elements in the original range. If $(D r) is also a forward range or + bidirectional range, the returned range will be likewise. +*/ +auto uniq(alias pred = "a == b", Range)(Range r) +if (isInputRange!Range && is(typeof(binaryFun!pred(r.front, r.front)) == bool)) +{ + return UniqResult!(binaryFun!pred, Range)(r); +} + +/// +@safe unittest +{ + import std.algorithm.mutation : copy; + import std.algorithm.comparison : equal; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][])); + + // Filter duplicates in-place using copy + arr.length -= arr.uniq().copy(arr).length; + assert(arr == [ 1, 2, 3, 4, 5 ]); + + // Note that uniqueness is only determined consecutively; duplicated + // elements separated by an intervening different element will not be + // eliminated: + assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1])); +} + +private struct UniqResult(alias pred, Range) +{ + Range _input; + + this(Range input) + { + _input = input; + } + + auto opSlice() + { + return this; + } + + void popFront() + { + auto last = _input.front; + do + { + _input.popFront(); + } + while (!_input.empty && pred(last, _input.front)); + } + + @property ElementType!Range front() { return _input.front; } + + static if (isBidirectionalRange!Range) + { + void popBack() + { + auto last = _input.back; + do + { + _input.popBack(); + } + while (!_input.empty && pred(last, _input.back)); + } + + @property ElementType!Range back() { return _input.back; } + } + + static if (isInfinite!Range) + { + enum bool empty = false; // Propagate infiniteness. + } + else + { + @property bool empty() { return _input.empty; } + } + + static if (isForwardRange!Range) { + @property typeof(this) save() { + return typeof(this)(_input.save); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.range; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + auto r = uniq(arr); + static assert(isForwardRange!(typeof(r))); + + assert(equal(r, [ 1, 2, 3, 4, 5 ][])); + assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][]))); + + foreach (DummyType; AllDummyRanges) { + DummyType d; + auto u = uniq(d); + assert(equal(u, [1,2,3,4,5,6,7,8,9,10])); + + static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u))); + + static if (d.rt >= RangeType.Bidirectional) { + assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1])); + } + } +} + diff --git a/std/algorithm/mutation.d b/std/algorithm/mutation.d new file mode 100644 index 00000000000..7d06d1ea5e3 --- /dev/null +++ b/std/algorithm/mutation.d @@ -0,0 +1,2203 @@ +// Written in the D programming language. +/** +This is a submodule of $(LINK2 std_algorithm_package.html, std.algorithm). +It contains generic _mutation algorithms. + +$(BOOKTABLE Cheat Sheet, + +$(TR $(TH Function Name) $(TH Description)) + +$(T2 bringToFront, + If $(D a = [1, 2, 3]) and $(D b = [4, 5, 6, 7]), + $(D bringToFront(a, b)) leaves $(D a = [4, 5, 6]) and + $(D b = [7, 1, 2, 3]).) +$(T2 copy, + Copies a range to another. If + $(D a = [1, 2, 3]) and $(D b = new int[5]), then $(D copy(a, b)) + leaves $(D b = [1, 2, 3, 0, 0]) and returns $(D b[3 .. $]).) +$(T2 fill, + Fills a range with a pattern, + e.g., if $(D a = new int[3]), then $(D fill(a, 4)) + leaves $(D a = [4, 4, 4]) and $(D fill(a, [3, 4])) leaves + $(D a = [3, 4, 3]).) +$(T2 initializeAll, + If $(D a = [1.2, 3.4]), then $(D initializeAll(a)) leaves + $(D a = [double.init, double.init]).) +$(T2 move, + $(D move(a, b)) moves $(D a) into $(D b). $(D move(a)) reads $(D a) + destructively.) +$(T2 moveAll, + Moves all elements from one range to another.) +$(T2 moveSome, + Moves as many elements as possible from one range to another.) +$(T2 remove, + Removes elements from a range in-place, and returns the shortened + range.) +$(T2 reverse, + If $(D a = [1, 2, 3]), $(D reverse(a)) changes it to $(D [3, 2, 1]).) +$(T2 strip, + Strips all leading and trailing elements equal to a value, or that + satisfy a predicate. + If $(D a = [1, 1, 0, 1, 1]), then $(D strip(a, 1)) and + $(D strip!(e => e == 1)(a)) returns $(D [0]).) +$(T2 stripLeft, + Strips all leading elements equal to a value, or that satisfy a + predicate. If $(D a = [1, 1, 0, 1, 1]), then $(D stripLeft(a, 1)) and + $(D stripLeft!(e => e == 1)(a)) returns $(D [0, 1, 1]).) +$(T2 stripRight, + Strips all trailing elements equal to a value, or that satisfy a + predicate. + If $(D a = [1, 1, 0, 1, 1]), then $(D stripRight(a, 1)) and + $(D stripRight!(e => e == 1)(a)) returns $(D [1, 1, 0]).) +$(T2 swap, + Swaps two values.) +$(T2 swapRanges, + Swaps all elements of two ranges.) +$(T2 uninitializedFill, + Fills a range (assumed uninitialized) with a value.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_mutation.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.mutation; + +import std.range.primitives; +import std.traits : isBlitAssignable, isNarrowString; +// FIXME +import std.typecons; // : tuple, Tuple; + +// FIXME: somehow deleting this breaks the bringToFront() unittests. +import std.range; + +// bringToFront +/** +The $(D bringToFront) function has considerable flexibility and +usefulness. It can rotate elements in one buffer left or right, swap +buffers of equal length, and even move elements across disjoint +buffers of different types and different lengths. + +$(D bringToFront) takes two ranges $(D front) and $(D back), which may +be of different types. Considering the concatenation of $(D front) and +$(D back) one unified range, $(D bringToFront) rotates that unified +range such that all elements in $(D back) are brought to the beginning +of the unified range. The relative ordering of elements in $(D front) +and $(D back), respectively, remains unchanged. + +Performs $(BIGOH max(front.length, back.length)) evaluations of $(D +swap). + +Preconditions: + +Either $(D front) and $(D back) are disjoint, or $(D back) is +reachable from $(D front) and $(D front) is not reachable from $(D +back). + +Returns: + +The number of elements brought to the front, i.e., the length of $(D +back). + +See_Also: + $(WEB sgi.com/tech/stl/_rotate.html, STL's rotate) +*/ +size_t bringToFront(Range1, Range2)(Range1 front, Range2 back) + if (isInputRange!Range1 && isForwardRange!Range2) +{ + import std.range: Take, take; + enum bool sameHeadExists = is(typeof(front.sameHead(back))); + size_t result; + for (bool semidone; !front.empty && !back.empty; ) + { + static if (sameHeadExists) + { + if (front.sameHead(back)) break; // shortcut + } + // Swap elements until front and/or back ends. + auto back0 = back.save; + size_t nswaps; + do + { + static if (sameHeadExists) + { + // Detect the stepping-over condition. + if (front.sameHead(back0)) back0 = back.save; + } + swapFront(front, back); + ++nswaps; + front.popFront(); + back.popFront(); + } + while (!front.empty && !back.empty); + + if (!semidone) result += nswaps; + + // Now deal with the remaining elements. + if (back.empty) + { + if (front.empty) break; + // Right side was shorter, which means that we've brought + // all the back elements to the front. + semidone = true; + // Next pass: bringToFront(front, back0) to adjust the rest. + back = back0; + } + else + { + assert(front.empty); + // Left side was shorter. Let's step into the back. + static if (is(Range1 == Take!Range2)) + { + front = take(back0, nswaps); + } + else + { + immutable subresult = bringToFront(take(back0, nswaps), + back); + if (!semidone) result += subresult; + break; // done + } + } + } + return result; +} + +/** +The simplest use of $(D bringToFront) is for rotating elements in a +buffer. For example: +*/ +@safe unittest +{ + auto arr = [4, 5, 6, 7, 1, 2, 3]; + auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); + assert(p == arr.length - 4); + assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]); +} + +/** +The $(D front) range may actually "step over" the $(D back) +range. This is very useful with forward ranges that cannot compute +comfortably right-bounded subranges like $(D arr[0 .. 4]) above. In +the example below, $(D r2) is a right subrange of $(D r1). +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + auto list = SList!(int)(4, 5, 6, 7, 1, 2, 3); + auto r1 = list[]; + auto r2 = list[]; popFrontN(r2, 4); + assert(equal(r2, [ 1, 2, 3 ])); + bringToFront(r1, r2); + assert(equal(list[], [ 1, 2, 3, 4, 5, 6, 7 ])); +} + + +/** +Elements can be swapped across ranges of different types: +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + auto list = SList!(int)(4, 5, 6, 7); + auto vec = [ 1, 2, 3 ]; + bringToFront(list[], vec); + assert(equal(list[], [ 1, 2, 3, 4 ])); + assert(equal(vec, [ 5, 6, 7 ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + import std.random : Random, unpredictableSeed, uniform; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + // a more elaborate test + { + auto rnd = Random(unpredictableSeed); + int[] a = new int[uniform(100, 200, rnd)]; + int[] b = new int[uniform(100, 200, rnd)]; + foreach (ref e; a) e = uniform(-100, 100, rnd); + foreach (ref e; b) e = uniform(-100, 100, rnd); + int[] c = a ~ b; + // writeln("a= ", a); + // writeln("b= ", b); + auto n = bringToFront(c[0 .. a.length], c[a.length .. $]); + //writeln("c= ", c); + assert(n == b.length); + assert(c == b ~ a, text(c, "\n", a, "\n", b)); + } + // different types, moveFront, no sameHead + { + static struct R(T) + { + T[] data; + size_t i; + @property + { + R save() { return this; } + bool empty() { return i >= data.length; } + T front() { return data[i]; } + T front(real e) { return data[i] = cast(T) e; } + } + void popFront() { ++i; } + } + auto a = R!int([1, 2, 3, 4, 5]); + auto b = R!real([6, 7, 8, 9]); + auto n = bringToFront(a, b); + assert(n == 4); + assert(a.data == [6, 7, 8, 9, 1]); + assert(b.data == [2, 3, 4, 5]); + } + // front steps over back + { + int[] arr, r1, r2; + + // back is shorter + arr = [4, 5, 6, 7, 1, 2, 3]; + r1 = arr; + r2 = arr[4 .. $]; + bringToFront(r1, r2) == 3 || assert(0); + assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); + + // front is shorter + arr = [5, 6, 7, 1, 2, 3, 4]; + r1 = arr; + r2 = arr[3 .. $]; + bringToFront(r1, r2) == 4 || assert(0); + assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); + } +} + +// copy +/** +Copies the content of $(D source) into $(D target) and returns the +remaining (unfilled) part of $(D target). + +Preconditions: $(D target) shall have enough room to accomodate +$(D source). + +See_Also: + $(WEB sgi.com/tech/stl/_copy.html, STL's _copy) + */ +Range2 copy(Range1, Range2)(Range1 source, Range2 target) +if (isInputRange!Range1 && isOutputRange!(Range2, ElementType!Range1)) +{ + static Range2 genericImpl(Range1 source, Range2 target) + { + // Specialize for 2 random access ranges. + // Typically 2 random access ranges are faster iterated by common + // index then by x.popFront(), y.popFront() pair + static if (isRandomAccessRange!Range1 && hasLength!Range1 + && hasSlicing!Range2 && isRandomAccessRange!Range2 && hasLength!Range2) + { + assert(target.length >= source.length, + "Cannot copy a source range into a smaller target range."); + + auto len = source.length; + foreach (idx; 0 .. len) + target[idx] = source[idx]; + return target[len .. target.length]; + } + else + { + put(target, source); + return target; + } + } + + import std.traits : isArray; + static if (isArray!Range1 && isArray!Range2 && + is(Unqual!(typeof(source[0])) == Unqual!(typeof(target[0])))) + { + immutable overlaps = () @trusted { + return source.ptr < target.ptr + target.length && + target.ptr < source.ptr + source.length; }(); + + if (overlaps) + { + return genericImpl(source, target); + } + else + { + // Array specialization. This uses optimized memory copying + // routines under the hood and is about 10-20x faster than the + // generic implementation. + assert(target.length >= source.length, + "Cannot copy a source array into a smaller target array."); + target[0..source.length] = source[]; + + return target[source.length..$]; + } + } + else + { + return genericImpl(source, target); + } +} + +/// +@safe unittest +{ + int[] a = [ 1, 5 ]; + int[] b = [ 9, 8 ]; + int[] buf = new int[a.length + b.length + 10]; + auto rem = copy(a, buf); // copy a into buf + rem = copy(b, rem); // copy b into remainder of buf + assert(buf[0 .. a.length + b.length] == [1, 5, 9, 8]); + assert(rem.length == 10); // unused slots in buf +} + +/** +As long as the target range elements support assignment from source +range elements, different types of ranges are accepted: +*/ +@safe unittest +{ + float[] src = [ 1.0f, 5 ]; + double[] dest = new double[src.length]; + copy(src, dest); +} + +/** +To _copy at most $(D n) elements from a range, you may want to use +$(XREF range, take): +*/ +@safe unittest +{ + import std.range; + int[] src = [ 1, 5, 8, 9, 10 ]; + auto dest = new int[3]; + copy(take(src, dest.length), dest); + assert(dest[0 .. $] == [ 1, 5, 8 ]); +} + +/** +To _copy just those elements from a range that satisfy a predicate you +may want to use $(LREF filter): +*/ +@safe unittest +{ + import std.algorithm.iteration : filter; + int[] src = [ 1, 5, 8, 9, 10, 1, 2, 0 ]; + auto dest = new int[src.length]; + auto rem = copy(src.filter!(a => (a & 1) == 1), dest); + assert(dest[0 .. $ - rem.length] == [ 1, 5, 9, 1 ]); +} + +/** +$(XREF range, retro) can be used to achieve behavior similar to +$(WEB sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): +*/ +@safe unittest +{ + import std.algorithm, std.range; + int[] src = [1, 2, 4]; + int[] dest = [0, 0, 0, 0, 0]; + copy(src.retro, dest.retro); + assert(dest == [0, 0, 1, 2, 4]); +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + { + int[] a = [ 1, 5 ]; + int[] b = [ 9, 8 ]; + auto e = copy(filter!("a > 1")(a), b); + assert(b[0] == 5 && e.length == 1); + } + + { + int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + copy(a[5..10], a[4..9]); + assert(a[4..9] == [6, 7, 8, 9, 10]); + } + + { // Test for bug 7898 + enum v = + { + import std.algorithm; + int[] arr1 = [10, 20, 30, 40, 50]; + int[] arr2 = arr1.dup; + copy(arr1, arr2); + return 35; + }(); + } +} + +/** +Assigns $(D value) to each element of input range $(D range). + +Params: + range = An $(XREF2 range, isInputRange, input range) that exposes references to its elements + and has assignable elements + value = Assigned to each element of range + +See_Also: + $(LREF uninitializedFill) + $(LREF initializeAll) + */ +void fill(Range, Value)(Range range, Value value) + if (isInputRange!Range && is(typeof(range.front = value))) +{ + alias T = ElementType!Range; + + static if (is(typeof(range[] = value))) + { + range[] = value; + } + else static if (is(typeof(range[] = T(value)))) + { + range[] = T(value); + } + else + { + for ( ; !range.empty; range.popFront() ) + { + range.front = value; + } + } +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4 ]; + fill(a, 5); + assert(a == [ 5, 5, 5, 5 ]); +} + +@safe unittest +{ + import std.conv : text; + import std.internal.test.dummyrange; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = [ 1, 2, 3 ]; + fill(a, 6); + assert(a == [ 6, 6, 6 ], text(a)); + + void fun0() + { + foreach (i; 0 .. 1000) + { + foreach (ref e; a) e = 6; + } + } + void fun1() { foreach (i; 0 .. 1000) fill(a, 6); } + //void fun2() { foreach (i; 0 .. 1000) fill2(a, 6); } + //writeln(benchmark!(fun0, fun1, fun2)(10000)); + + // fill should accept InputRange + alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); + enum filler = uint.max; + InputRange range; + fill(range, filler); + foreach (value; range.arr) + assert(value == filler); +} + +@safe unittest +{ + //ER8638_1 IS_NOT self assignable + static struct ER8638_1 + { + void opAssign(int){} + } + + //ER8638_1 IS self assignable + static struct ER8638_2 + { + void opAssign(ER8638_2){} + void opAssign(int){} + } + + auto er8638_1 = new ER8638_1[](10); + auto er8638_2 = new ER8638_2[](10); + er8638_1.fill(5); //generic case + er8638_2.fill(5); //opSlice(T.init) case +} + +@safe unittest +{ + { + int[] a = [1, 2, 3]; + immutable(int) b = 0; + static assert(__traits(compiles, a.fill(b))); + } + { + double[] a = [1, 2, 3]; + immutable(int) b = 0; + static assert(__traits(compiles, a.fill(b))); + } +} + +/** +Fills $(D range) with a pattern copied from $(D filler). The length of +$(D range) does not have to be a multiple of the length of $(D +filler). If $(D filler) is empty, an exception is thrown. + +Params: + range = An $(XREF2 range, isInputRange, input range) that exposes + references to its elements and has assignable elements. + filler = The $(XREF2 range, isForwardRange, forward range) representing the + _fill pattern. + */ +void fill(Range1, Range2)(Range1 range, Range2 filler) + if (isInputRange!Range1 + && (isForwardRange!Range2 + || (isInputRange!Range2 && isInfinite!Range2)) + && is(typeof(Range1.init.front = Range2.init.front))) +{ + static if (isInfinite!Range2) + { + //Range2 is infinite, no need for bounds checking or saving + static if (hasSlicing!Range2 && hasLength!Range1 + && is(typeof(filler[0 .. range.length]))) + { + copy(filler[0 .. range.length], range); + } + else + { + //manual feed + for ( ; !range.empty; range.popFront(), filler.popFront()) + { + range.front = filler.front; + } + } + } + else + { + import std.exception : enforce; + + enforce(!filler.empty, "Cannot fill range with an empty filler"); + + static if (hasLength!Range1 && hasLength!Range2 + && is(typeof(range.length > filler.length))) + { + //Case we have access to length + auto len = filler.length; + //Start by bulk copies + while (range.length > len) + { + range = copy(filler.save, range); + } + + //and finally fill the partial range. No need to save here. + static if (hasSlicing!Range2 && is(typeof(filler[0 .. range.length]))) + { + //use a quick copy + auto len2 = range.length; + range = copy(filler[0 .. len2], range); + } + else + { + //iterate. No need to check filler, it's length is longer than range's + for (; !range.empty; range.popFront(), filler.popFront()) + { + range.front = filler.front; + } + } + } + else + { + //Most basic case. + auto bck = filler.save; + for (; !range.empty; range.popFront(), filler.popFront()) + { + if (filler.empty) filler = bck.save; + range.front = filler.front; + } + } + } +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4, 5 ]; + int[] b = [ 8, 9 ]; + fill(a, b); + assert(a == [ 8, 9, 8, 9, 8 ]); +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.internal.test.dummyrange; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = [ 1, 2, 3, 4, 5 ]; + int[] b = [1, 2]; + fill(a, b); + assert(a == [ 1, 2, 1, 2, 1 ]); + + // fill should accept InputRange + alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); + InputRange range; + fill(range,[1,2]); + foreach (i,value;range.arr) + assert(value == (i%2==0?1:2)); + + //test with a input being a "reference forward" range + fill(a, new ReferenceForwardRange!int([8, 9])); + assert(a == [8, 9, 8, 9, 8]); + + //test with a input being an "infinite input" range + fill(a, new ReferenceInfiniteInputRange!int()); + assert(a == [0, 1, 2, 3, 4]); + + //empty filler test + assertThrown(fill(a, a[$..$])); +} + +/** +Initializes all elements of $(D range) with their $(D .init) value. +Assumes that the elements of the range are uninitialized. + +Params: + range = An $(XREF2 range, isInputRange, input range) that exposes references to its elements + and has assignable elements + +See_Also: + $(LREF fill) + $(LREF uninitializeFill) + +Example: +---- +struct S { ... } +S[] s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5]; +initializeAll(s); +assert(s == [ 0, 0, 0, 0, 0 ]); +---- + */ +void initializeAll(Range)(Range range) + if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range) +{ + import core.stdc.string : memset, memcpy; + import std.traits : hasElaborateAssign, isDynamicArray; + + alias T = ElementType!Range; + static if (hasElaborateAssign!T) + { + import std.algorithm : addressOf; // FIXME + //Elaborate opAssign. Must go the memcpy road. + //We avoid calling emplace here, because our goal is to initialize to + //the static state of T.init, + //So we want to avoid any un-necassarilly CC'ing of T.init + auto p = typeid(T).init().ptr; + if (p) + for ( ; !range.empty ; range.popFront() ) + memcpy(addressOf(range.front), p, T.sizeof); + else + static if (isDynamicArray!Range) + memset(range.ptr, 0, range.length * T.sizeof); + else + for ( ; !range.empty ; range.popFront() ) + memset(addressOf(range.front), 0, T.sizeof); + } + else + fill(range, T.init); +} + +// ditto +void initializeAll(Range)(Range range) + if (is(Range == char[]) || is(Range == wchar[])) +{ + alias T = ElementEncodingType!Range; + range[] = T.init; +} + +unittest +{ + import std.algorithm.iteration : filter; + import std.traits : hasElaborateAssign; + import std.typetuple : TypeTuple; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + //Test strings: + //Must work on narrow strings. + //Must reject const + char[3] a = void; + a[].initializeAll(); + assert(a[] == [char.init, char.init, char.init]); + string s; + assert(!__traits(compiles, s.initializeAll())); + + //Note: Cannot call uninitializedFill on narrow strings + + enum e {e1, e2} + e[3] b1 = void; + b1[].initializeAll(); + assert(b1[] == [e.e1, e.e1, e.e1]); + e[3] b2 = void; + b2[].uninitializedFill(e.e2); + assert(b2[] == [e.e2, e.e2, e.e2]); + + static struct S1 + { + int i; + } + static struct S2 + { + int i = 1; + } + static struct S3 + { + int i; + this(this){} + } + static struct S4 + { + int i = 1; + this(this){} + } + static assert (!hasElaborateAssign!S1); + static assert (!hasElaborateAssign!S2); + static assert ( hasElaborateAssign!S3); + static assert ( hasElaborateAssign!S4); + assert (!typeid(S1).init().ptr); + assert ( typeid(S2).init().ptr); + assert (!typeid(S3).init().ptr); + assert ( typeid(S4).init().ptr); + + foreach(S; TypeTuple!(S1, S2, S3, S4)) + { + //initializeAll + { + //Array + S[3] ss1 = void; + ss1[].initializeAll(); + assert(ss1[] == [S.init, S.init, S.init]); + + //Not array + S[3] ss2 = void; + auto sf = ss2[].filter!"true"(); + + sf.initializeAll(); + assert(ss2[] == [S.init, S.init, S.init]); + } + //uninitializedFill + { + //Array + S[3] ss1 = void; + ss1[].uninitializedFill(S(2)); + assert(ss1[] == [S(2), S(2), S(2)]); + + //Not array + S[3] ss2 = void; + auto sf = ss2[].filter!"true"(); + sf.uninitializedFill(S(2)); + assert(ss2[] == [S(2), S(2), S(2)]); + } + } +} + +// move +/** +Moves $(D source) into $(D target) via a destructive copy. + +Params: + source = Data to copy. If a destructor or postblit is defined, it is reset + to its $(D .init) value after it is moved into target. Note that data + with internal pointers that point to itself cannot be moved, and will + trigger an assertion failure. + target = Where to copy into. The destructor, if any, is invoked before the + copy is performed. +*/ +void move(T)(ref T source, ref T target) +{ + import core.stdc.string : memcpy; + import std.traits : hasAliasing, hasElaborateAssign, + hasElaborateCopyConstructor, hasElaborateDestructor, + isAssignable; + + static if (!is( T == class) && hasAliasing!T) if (!__ctfe) + { + import std.exception : doesPointTo; + assert(!doesPointTo(source, source), "Cannot move object with internal pointer."); + } + + static if (is(T == struct)) + { + if (&source == &target) return; + // Most complicated case. Destroy whatever target had in it + // and bitblast source over it + static if (hasElaborateDestructor!T) typeid(T).destroy(&target); + + static if (hasElaborateAssign!T || !isAssignable!T) + memcpy(&target, &source, T.sizeof); + else + target = source; + + // If the source defines a destructor or a postblit hook, we must obliterate the + // object in order to avoid double freeing and undue aliasing + static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) + { + import std.algorithm.searching : endsWith; + + static T empty; + static if (T.tupleof.length > 0 && + T.tupleof[$-1].stringof.endsWith("this")) + { + // If T is nested struct, keep original context pointer + memcpy(&source, &empty, T.sizeof - (void*).sizeof); + } + else + { + memcpy(&source, &empty, T.sizeof); + } + } + } + else + { + // Primitive data (including pointers and arrays) or class - + // assignment works great + target = source; + } +} + +unittest +{ + import std.traits; + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + import std.exception : assertCTFEable; + assertCTFEable!((){ + Object obj1 = new Object; + Object obj2 = obj1; + Object obj3; + move(obj2, obj3); + assert(obj3 is obj1); + + static struct S1 { int a = 1, b = 2; } + S1 s11 = { 10, 11 }; + S1 s12; + move(s11, s12); + assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); + + static struct S2 { int a = 1; int * b; } + S2 s21 = { 10, null }; + s21.b = new int; + S2 s22; + move(s21, s22); + assert(s21 == s22); + }); + // Issue 5661 test(1) + static struct S3 + { + static struct X { int n = 0; ~this(){n = 0;} } + X x; + } + static assert(hasElaborateDestructor!S3); + S3 s31, s32; + s31.x.n = 1; + move(s31, s32); + assert(s31.x.n == 0); + assert(s32.x.n == 1); + + // Issue 5661 test(2) + static struct S4 + { + static struct X { int n = 0; this(this){n = 0;} } + X x; + } + static assert(hasElaborateCopyConstructor!S4); + S4 s41, s42; + s41.x.n = 1; + move(s41, s42); + assert(s41.x.n == 0); + assert(s42.x.n == 1); + + // Issue 13990 test + class S5; + + S5 s51; + static assert(__traits(compiles, move(s51, s51)), + "issue 13990, cannot move opaque class reference"); +} + +/// Ditto +T move(T)(ref T source) +{ + import core.stdc.string : memcpy; + import std.traits : hasAliasing, hasElaborateAssign, + hasElaborateCopyConstructor, hasElaborateDestructor, + isAssignable; + + static if (!is(T == class) && hasAliasing!T) if (!__ctfe) + { + import std.exception : doesPointTo; + assert(!doesPointTo(source, source), "Cannot move object with internal pointer."); + } + + T result = void; + static if (is(T == struct)) + { + // Can avoid destructing result. + static if (hasElaborateAssign!T || !isAssignable!T) + memcpy(&result, &source, T.sizeof); + else + result = source; + + // If the source defines a destructor or a postblit hook, we must obliterate the + // object in order to avoid double freeing and undue aliasing + static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) + { + import std.algorithm.searching : endsWith; + + static T empty; + static if (T.tupleof.length > 0 && + T.tupleof[$-1].stringof.endsWith("this")) + { + // If T is nested struct, keep original context pointer + memcpy(&source, &empty, T.sizeof - (void*).sizeof); + } + else + { + memcpy(&source, &empty, T.sizeof); + } + } + } + else + { + // Primitive data (including pointers and arrays) or class - + // assignment works great + result = source; + } + return result; +} + +unittest +{ + import std.traits; + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + import std.exception : assertCTFEable; + assertCTFEable!((){ + Object obj1 = new Object; + Object obj2 = obj1; + Object obj3 = move(obj2); + assert(obj3 is obj1); + + static struct S1 { int a = 1, b = 2; } + S1 s11 = { 10, 11 }; + S1 s12 = move(s11); + assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); + + static struct S2 { int a = 1; int * b; } + S2 s21 = { 10, null }; + s21.b = new int; + S2 s22 = move(s21); + assert(s21 == s22); + }); + + // Issue 5661 test(1) + static struct S3 + { + static struct X { int n = 0; ~this(){n = 0;} } + X x; + } + static assert(hasElaborateDestructor!S3); + S3 s31; + s31.x.n = 1; + S3 s32 = move(s31); + assert(s31.x.n == 0); + assert(s32.x.n == 1); + + // Issue 5661 test(2) + static struct S4 + { + static struct X { int n = 0; this(this){n = 0;} } + X x; + } + static assert(hasElaborateCopyConstructor!S4); + S4 s41; + s41.x.n = 1; + S4 s42 = move(s41); + assert(s41.x.n == 0); + assert(s42.x.n == 1); + + // Issue 13990 test + class S5; + + S5 s51; + static assert(__traits(compiles, s51 = move(s51)), + "issue 13990, cannot move opaque class reference"); +} + +unittest//Issue 6217 +{ + import std.algorithm.iteration : map; + auto x = map!"a"([1,2,3]); + x = move(x); +} + +unittest// Issue 8055 +{ + static struct S + { + int x; + ~this() + { + assert(x == 0); + } + } + S foo(S s) + { + return move(s); + } + S a; + a.x = 0; + auto b = foo(a); + assert(b.x == 0); +} + +unittest// Issue 8057 +{ + int n = 10; + struct S + { + int x; + ~this() + { + // Access to enclosing scope + assert(n == 10); + } + } + S foo(S s) + { + // Move nested struct + return move(s); + } + S a; + a.x = 1; + auto b = foo(a); + assert(b.x == 1); + + // Regression 8171 + static struct Array(T) + { + // nested struct has no member + struct Payload + { + ~this() {} + } + } + Array!int.Payload x = void; + static assert(__traits(compiles, move(x) )); + static assert(__traits(compiles, move(x, x) )); +} + +// moveAll +/** +For each element $(D a) in $(D src) and each element $(D b) in $(D +tgt) in lockstep in increasing order, calls $(D move(a, b)). + +Preconditions: +$(D walkLength(src) <= walkLength(tgt)). +An exception will be thrown if this condition does not hold, i.e., there is not +enough room in $(D tgt) to accommodate all of $(D src). + +Params: + src = An $(XREF2 range, isInputRange, input range) with movable elements. + tgt = An $(XREF2 range, isInputRange, input range) with elements that + elements from $(D src) can be moved into. + +Returns: The leftover portion of $(D tgt) after all elements from $(D src) have +been moved. + */ +Range2 moveAll(Range1, Range2)(Range1 src, Range2 tgt) +if (isInputRange!Range1 && isInputRange!Range2 + && is(typeof(move(src.front, tgt.front)))) +{ + import std.exception : enforce; + + static if (isRandomAccessRange!Range1 && hasLength!Range1 && hasLength!Range2 + && hasSlicing!Range2 && isRandomAccessRange!Range2) + { + auto toMove = src.length; + enforce(toMove <= tgt.length); // shouldn't this be an assert? + foreach (idx; 0 .. toMove) + move(src[idx], tgt[idx]); + return tgt[toMove .. tgt.length]; + } + else + { + for (; !src.empty; src.popFront(), tgt.popFront()) + { + enforce(!tgt.empty); //ditto? + move(src.front, tgt.front); + } + return tgt; + } +} + +unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 1, 2, 3 ]; + int[] b = new int[5]; + assert(moveAll(a, b) is b[3 .. $]); + assert(a == b[0 .. 3]); + assert(a == [ 1, 2, 3 ]); +} + +// moveSome +/** +For each element $(D a) in $(D src) and each element $(D b) in $(D +tgt) in lockstep in increasing order, calls $(D move(a, b)). Stops +when either $(D src) or $(D tgt) have been exhausted. + +Params: + src = An $(XREF2 range, isInputRange, input range) with movable elements. + tgt = An $(XREF2 range, isInputRange, input range) with elements that + elements from $(D src) can be moved into. + +Returns: The leftover portions of the two ranges after one or the other of the +ranges have been exhausted. + */ +Tuple!(Range1, Range2) moveSome(Range1, Range2)(Range1 src, Range2 tgt) +if (isInputRange!Range1 && isInputRange!Range2 + && is(typeof(move(src.front, tgt.front)))) +{ + import std.exception : enforce; + + for (; !src.empty && !tgt.empty; src.popFront(), tgt.popFront()) + { + enforce(!tgt.empty); + move(src.front, tgt.front); + } + return tuple(src, tgt); +} + +unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 1, 2, 3, 4, 5 ]; + int[] b = new int[3]; + assert(moveSome(a, b)[0] is a[3 .. $]); + assert(a[0 .. 3] == b); + assert(a == [ 1, 2, 3, 4, 5 ]); +} + +// SwapStrategy +/** +Defines the swapping strategy for algorithms that need to swap +elements in a range (such as partition and sort). The strategy +concerns the swapping of elements that are not the core concern of the +algorithm. For example, consider an algorithm that sorts $(D [ "abc", +"b", "aBc" ]) according to $(D toUpper(a) < toUpper(b)). That +algorithm might choose to swap the two equivalent strings $(D "abc") +and $(D "aBc"). That does not affect the sorting since both $(D [ +"abc", "aBc", "b" ]) and $(D [ "aBc", "abc", "b" ]) are valid +outcomes. + +Some situations require that the algorithm must NOT ever change the +relative ordering of equivalent elements (in the example above, only +$(D [ "abc", "aBc", "b" ]) would be the correct result). Such +algorithms are called $(B stable). If the ordering algorithm may swap +equivalent elements discretionarily, the ordering is called $(B +unstable). + +Yet another class of algorithms may choose an intermediate tradeoff by +being stable only on a well-defined subrange of the range. There is no +established terminology for such behavior; this library calls it $(B +semistable). + +Generally, the $(D stable) ordering strategy may be more costly in +time and/or space than the other two because it imposes additional +constraints. Similarly, $(D semistable) may be costlier than $(D +unstable). As (semi-)stability is not needed very often, the ordering +algorithms in this module parameterized by $(D SwapStrategy) all +choose $(D SwapStrategy.unstable) as the default. +*/ + +enum SwapStrategy +{ + /** + Allows freely swapping of elements as long as the output + satisfies the algorithm's requirements. + */ + unstable, + /** + In algorithms partitioning ranges in two, preserve relative + ordering of elements only to the left of the partition point. + */ + semistable, + /** + Preserve the relative ordering of elements to the largest + extent allowed by the algorithm's requirements. + */ + stable, +} + +/** +Eliminates elements at given offsets from $(D range) and returns the +shortened range. In the simplest call, one element is removed. + +---- +int[] a = [ 3, 5, 7, 8 ]; +assert(remove(a, 1) == [ 3, 7, 8 ]); +assert(a == [ 3, 7, 8, 8 ]); +---- + +In the case above the element at offset $(D 1) is removed and $(D +remove) returns the range smaller by one element. The original array +has remained of the same length because all functions in $(D +std.algorithm) only change $(I content), not $(I topology). The value +$(D 8) is repeated because $(XREF algorithm, move) was invoked to move +elements around and on integers $(D move) simply copies the source to +the destination. To replace $(D a) with the effect of the removal, +simply assign $(D a = remove(a, 1)). The slice will be rebound to the +shorter array and the operation completes with maximal efficiency. + +Multiple indices can be passed into $(D remove). In that case, +elements at the respective indices are all removed. The indices must +be passed in increasing order, otherwise an exception occurs. + +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; +assert(remove(a, 1, 3, 5) == + [ 0, 2, 4, 6, 7, 8, 9, 10 ]); +---- + +(Note how all indices refer to slots in the $(I original) array, not +in the array as it is being progressively shortened.) Finally, any +combination of integral offsets and tuples composed of two integral +offsets can be passed in. + +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; +assert(remove(a, 1, tuple(3, 5), 9) == [ 0, 2, 6, 7, 8, 10 ]); +---- + +In this case, the slots at positions 1, 3, 4, and 9 are removed from +the array. The tuple passes in a range closed to the left and open to +the right (consistent with built-in slices), e.g. $(D tuple(3, 5)) +means indices $(D 3) and $(D 4) but not $(D 5). + +If the need is to remove some elements in the range but the order of +the remaining elements does not have to be preserved, you may want to +pass $(D SwapStrategy.unstable) to $(D remove). + +---- +int[] a = [ 0, 1, 2, 3 ]; +assert(remove!(SwapStrategy.unstable)(a, 1) == [ 0, 3, 2 ]); +---- + +In the case above, the element at slot $(D 1) is removed, but replaced +with the last element of the range. Taking advantage of the relaxation +of the stability requirement, $(D remove) moved elements from the end +of the array over the slots to be removed. This way there is less data +movement to be done which improves the execution time of the function. + +The function $(D remove) works on any forward range. The moving +strategy is (listed from fastest to slowest): $(UL $(LI If $(D s == +SwapStrategy.unstable && isRandomAccessRange!Range && hasLength!Range +&& hasLvalueElements!Range), then elements are moved from the end +of the range into the slots to be filled. In this case, the absolute +minimum of moves is performed.) $(LI Otherwise, if $(D s == +SwapStrategy.unstable && isBidirectionalRange!Range && hasLength!Range +&& hasLvalueElements!Range), then elements are still moved from the +end of the range, but time is spent on advancing between slots by repeated +calls to $(D range.popFront).) $(LI Otherwise, elements are moved +incrementally towards the front of $(D range); a given element is never +moved several times, but more elements are moved than in the previous +cases.)) + */ +Range remove +(SwapStrategy s = SwapStrategy.stable, Range, Offset...) +(Range range, Offset offset) +if (s != SwapStrategy.stable + && isBidirectionalRange!Range + && hasLvalueElements!Range + && hasLength!Range + && Offset.length >= 1) +{ + import std.algorithm.comparison : min; + + Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts; + foreach (i, v; offset) + { + static if (is(typeof(v[0]) : size_t) && is(typeof(v[1]) : size_t)) + { + blackouts[i].pos = v[0]; + blackouts[i].len = v[1] - v[0]; + } + else + { + static assert(is(typeof(v) : size_t), typeof(v).stringof); + blackouts[i].pos = v; + blackouts[i].len = 1; + } + static if (i > 0) + { + import std.exception : enforce; + + enforce(blackouts[i - 1].pos + blackouts[i - 1].len + <= blackouts[i].pos, + "remove(): incorrect ordering of elements to remove"); + } + } + + size_t left = 0, right = offset.length - 1; + auto tgt = range.save; + size_t steps = 0; + + while (left <= right) + { + // Look for a blackout on the right + if (blackouts[right].pos + blackouts[right].len >= range.length) + { + range.popBackExactly(blackouts[right].len); + + // Since right is unsigned, we must check for this case, otherwise + // we might turn it into size_t.max and the loop condition will not + // fail when it should. + if (right > 0) + { + --right; + continue; + } + else + break; + } + // Advance to next blackout on the left + assert(blackouts[left].pos >= steps); + tgt.popFrontExactly(blackouts[left].pos - steps); + steps = blackouts[left].pos; + auto toMove = min( + blackouts[left].len, + range.length - (blackouts[right].pos + blackouts[right].len)); + foreach (i; 0 .. toMove) + { + move(range.back, tgt.front); + range.popBack(); + tgt.popFront(); + } + steps += toMove; + if (toMove == blackouts[left].len) + { + // Filled the entire left hole + ++left; + continue; + } + } + + return range; +} + +// Ditto +Range remove +(SwapStrategy s = SwapStrategy.stable, Range, Offset...) +(Range range, Offset offset) +if (s == SwapStrategy.stable + && isBidirectionalRange!Range + && hasLvalueElements!Range + && Offset.length >= 1) +{ + auto result = range; + auto src = range, tgt = range; + size_t pos; + foreach (pass, i; offset) + { + static if (is(typeof(i[0])) && is(typeof(i[1]))) + { + auto from = i[0], delta = i[1] - i[0]; + } + else + { + auto from = i; + enum delta = 1; + } + + static if (pass > 0) + { + import std.exception : enforce; + enforce(pos <= from, + "remove(): incorrect ordering of elements to remove"); + + for (; pos < from; ++pos, src.popFront(), tgt.popFront()) + { + move(src.front, tgt.front); + } + } + else + { + src.popFrontExactly(from); + tgt.popFrontExactly(from); + pos = from; + } + // now skip source to the "to" position + src.popFrontExactly(delta); + result.popBackExactly(delta); + pos += delta; + } + // leftover move + moveAll(src, tgt); + return result; +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.range; + + // http://d.puremagic.com/issues/show_bug.cgi?id=10173 + int[] test = iota(0, 10).array(); + assertThrown(remove!(SwapStrategy.stable)(test, tuple(2, 4), tuple(1, 3))); + assertThrown(remove!(SwapStrategy.unstable)(test, tuple(2, 4), tuple(1, 3))); + assertThrown(remove!(SwapStrategy.stable)(test, 2, 4, 1, 3)); + assertThrown(remove!(SwapStrategy.unstable)(test, 2, 4, 1, 3)); +} + +@safe unittest +{ + import std.range; + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + //writeln(remove!(SwapStrategy.stable)(a, 1)); + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1) == + [ 0, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.unstable)(a, 0, 10) == + [ 9, 1, 2, 3, 4, 5, 6, 7, 8 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.unstable)(a, 0, tuple(9, 11)) == + [ 8, 1, 2, 3, 4, 5, 6, 7 ]); + // http://d.puremagic.com/issues/show_bug.cgi?id=5224 + a = [ 1, 2, 3, 4 ]; + assert(remove!(SwapStrategy.unstable)(a, 2) == + [ 1, 2, 4 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + //writeln(remove!(SwapStrategy.stable)(a, 1, 5)); + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1, 5) == + [ 0, 2, 3, 4, 6, 7, 8, 9, 10 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + //writeln(remove!(SwapStrategy.stable)(a, 1, 3, 5)); + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1, 3, 5) + == [ 0, 2, 4, 6, 7, 8, 9, 10]); + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + //writeln(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5))); + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5)) + == [ 0, 2, 5, 6, 7, 8, 9, 10]); + + a = iota(0, 10).array(); + assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7)) + == [0, 9, 8, 7, 4, 5]); +} + +@safe unittest +{ + // Issue 11576 + auto arr = [1,2,3]; + arr = arr.remove!(SwapStrategy.unstable)(2); + assert(arr == [1,2]); + +} + +@safe unittest +{ + import std.range; + // Bug# 12889 + int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]]; + auto orig = arr.dup; + foreach (i; iota(arr.length)) + { + assert(orig == arr.remove!(SwapStrategy.unstable)(tuple(i,i))); + assert(orig == arr.remove!(SwapStrategy.stable)(tuple(i,i))); + } +} + +/** +Reduces the length of the bidirectional range $(D range) by removing +elements that satisfy $(D pred). If $(D s = SwapStrategy.unstable), +elements are moved from the right end of the range over the elements +to eliminate. If $(D s = SwapStrategy.stable) (the default), +elements are moved progressively to front such that their relative +order is preserved. Returns the filtered range. +*/ +Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range) +(Range range) +if (isBidirectionalRange!Range + && hasLvalueElements!Range) +{ + import std.functional : unaryFun; + auto result = range; + static if (s != SwapStrategy.stable) + { + for (;!range.empty;) + { + if (!unaryFun!pred(range.front)) + { + range.popFront(); + continue; + } + move(range.back, range.front); + range.popBack(); + result.popBack(); + } + } + else + { + auto tgt = range; + for (; !range.empty; range.popFront()) + { + if (unaryFun!(pred)(range.front)) + { + // yank this guy + result.popBack(); + continue; + } + // keep this guy + move(range.front, tgt.front); + tgt.popFront(); + } + } + return result; +} + +/// +@safe unittest +{ + static immutable base = [1, 2, 3, 2, 4, 2, 5, 2]; + + int[] arr = base[].dup; + + // using a string-based predicate + assert(remove!("a == 2")(arr) == [ 1, 3, 4, 5 ]); + + // The original array contents have been modified, + // so we need to reset it to its original state. + // The length is unmodified however. + arr[] = base[]; + + // using a lambda predicate + assert(remove!(a => a == 2)(arr) == [ 1, 3, 4, 5 ]); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; + assert(remove!("a == 2", SwapStrategy.unstable)(a) == + [ 1, 6, 3, 5, 3, 4, 5 ]); + a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; + //writeln(remove!("a != 2", SwapStrategy.stable)(a)); + assert(remove!("a == 2", SwapStrategy.stable)(a) == + [ 1, 3, 3, 4, 5, 5, 6 ]); +} + +// reverse +/** +Reverses $(D r) in-place. Performs $(D r.length / 2) evaluations of $(D +swap). + +See_Also: + $(WEB sgi.com/tech/stl/_reverse.html, STL's _reverse) +*/ +void reverse(Range)(Range r) +if (isBidirectionalRange!Range && !isRandomAccessRange!Range + && hasSwappableElements!Range) +{ + while (!r.empty) + { + swap(r.front, r.back); + r.popFront(); + if (r.empty) break; + r.popBack(); + } +} + +/// +@safe unittest +{ + int[] arr = [ 1, 2, 3 ]; + reverse(arr); + assert(arr == [ 3, 2, 1 ]); +} + +///ditto +void reverse(Range)(Range r) +if (isRandomAccessRange!Range && hasLength!Range) +{ + //swapAt is in fact the only way to swap non lvalue ranges + immutable last = r.length-1; + immutable steps = r.length/2; + for (size_t i = 0; i < steps; i++) + { + swapAt(r, i, last-i); + } +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] range = null; + reverse(range); + range = [ 1 ]; + reverse(range); + assert(range == [1]); + range = [1, 2]; + reverse(range); + assert(range == [2, 1]); + range = [1, 2, 3]; + reverse(range); + assert(range == [3, 2, 1]); +} + +/** +Reverses $(D r) in-place, where $(D r) is a narrow string (having +elements of type $(D char) or $(D wchar)). UTF sequences consisting of +multiple code units are preserved properly. +*/ +void reverse(Char)(Char[] s) +if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable)) +{ + import std.string : representation; + import std.utf : stride; + + auto r = representation(s); + for (size_t i = 0; i < s.length; ) + { + immutable step = std.utf.stride(s, i); + if (step > 1) + { + .reverse(r[i .. i + step]); + i += step; + } + else + { + ++i; + } + } + reverse(r); +} + +/// +@safe unittest +{ + char[] arr = "hello\U00010143\u0100\U00010143".dup; + reverse(arr); + assert(arr == "\U00010143\u0100\U00010143olleh"); +} + +@safe unittest +{ + void test(string a, string b) + { + auto c = a.dup; + reverse(c); + assert(c == b, c ~ " != " ~ b); + } + + test("a", "a"); + test(" ", " "); + test("\u2029", "\u2029"); + test("\u0100", "\u0100"); + test("\u0430", "\u0430"); + test("\U00010143", "\U00010143"); + test("abcdefcdef", "fedcfedcba"); + test("hello\U00010143\u0100\U00010143", "\U00010143\u0100\U00010143olleh"); +} + +//private +void swapAt(R)(R r, size_t i1, size_t i2) +{ + static if (is(typeof(&r[i1]))) + { + swap(r[i1], r[i2]); + } + else + { + if (i1 == i2) return; + auto t1 = moveAt(r, i1); + auto t2 = moveAt(r, i2); + r[i2] = t1; + r[i1] = t2; + } +} + +/** + The strip group of functions allow stripping of either leading, trailing, + or both leading and trailing elements. + + The $(D stripLeft) function will strip the $(D front) of the range, + the $(D stripRight) function will strip the $(D back) of the range, + while the $(D strip) function will strip both the $(D front) and $(D back) + of the range. + + Note that the $(D strip) and $(D stripRight) functions require the range to + be a $(LREF BidirectionalRange) range. + + All of these functions come in two varieties: one takes a target element, + where the range will be stripped as long as this element can be found. + The other takes a lambda predicate, where the range will be stripped as + long as the predicate returns true. +*/ +Range strip(Range, E)(Range range, E element) + if (isBidirectionalRange!Range && is(typeof(range.front == element) : bool)) +{ + return range.stripLeft(element).stripRight(element); +} + +/// ditto +Range strip(alias pred, Range)(Range range) + if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) +{ + return range.stripLeft!pred().stripRight!pred(); +} + +/// ditto +Range stripLeft(Range, E)(Range range, E element) + if (isInputRange!Range && is(typeof(range.front == element) : bool)) +{ + import std.algorithm.searching : find; + return find!((auto ref a) => a != element)(range); +} + +/// ditto +Range stripLeft(alias pred, Range)(Range range) + if (isInputRange!Range && is(typeof(pred(range.front)) : bool)) +{ + import std.algorithm.searching : find; + import std.functional : not; + + return find!(not!pred)(range); +} + +/// ditto +Range stripRight(Range, E)(Range range, E element) + if (isBidirectionalRange!Range && is(typeof(range.back == element) : bool)) +{ + for (; !range.empty; range.popBack()) + { + if (range.back != element) + break; + } + return range; +} + +/// ditto +Range stripRight(alias pred, Range)(Range range) + if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) +{ + for (; !range.empty; range.popBack()) + { + if (!pred(range.back)) + break; + } + return range; +} + +/// Strip leading and trailing elements equal to the target element. +@safe pure unittest +{ + assert(" foobar ".strip(' ') == "foobar"); + assert("00223.444500".strip('0') == "223.4445"); + assert("ëëêéüŗōpéêëë".strip('ë') == "êéüŗōpéê"); + assert([1, 1, 0, 1, 1].strip(1) == [0]); + assert([0.0, 0.01, 0.01, 0.0].strip(0).length == 2); +} + +/// Strip leading and trailing elements while the predicate returns true. +@safe pure unittest +{ + assert(" foobar ".strip!(a => a == ' ')() == "foobar"); + assert("00223.444500".strip!(a => a == '0')() == "223.4445"); + assert("ëëêéüŗōpéêëë".strip!(a => a == 'ë')() == "êéüŗōpéê"); + assert([1, 1, 0, 1, 1].strip!(a => a == 1)() == [0]); + assert([0.0, 0.01, 0.5, 0.6, 0.01, 0.0].strip!(a => a < 0.4)().length == 2); +} + +/// Strip leading elements equal to the target element. +@safe pure unittest +{ + assert(" foobar ".stripLeft(' ') == "foobar "); + assert("00223.444500".stripLeft('0') == "223.444500"); + assert("ůůűniçodêéé".stripLeft('ů') == "űniçodêéé"); + assert([1, 1, 0, 1, 1].stripLeft(1) == [0, 1, 1]); + assert([0.0, 0.01, 0.01, 0.0].stripLeft(0).length == 3); +} + +/// Strip leading elements while the predicate returns true. +@safe pure unittest +{ + assert(" foobar ".stripLeft!(a => a == ' ')() == "foobar "); + assert("00223.444500".stripLeft!(a => a == '0')() == "223.444500"); + assert("ůůűniçodêéé".stripLeft!(a => a == 'ů')() == "űniçodêéé"); + assert([1, 1, 0, 1, 1].stripLeft!(a => a == 1)() == [0, 1, 1]); + assert([0.0, 0.01, 0.10, 0.5, 0.6].stripLeft!(a => a < 0.4)().length == 2); +} + +/// Strip trailing elements equal to the target element. +@safe pure unittest +{ + assert(" foobar ".stripRight(' ') == " foobar"); + assert("00223.444500".stripRight('0') == "00223.4445"); + assert("ùniçodêéé".stripRight('é') == "ùniçodê"); + assert([1, 1, 0, 1, 1].stripRight(1) == [1, 1, 0]); + assert([0.0, 0.01, 0.01, 0.0].stripRight(0).length == 3); +} + +/// Strip trailing elements while the predicate returns true. +@safe pure unittest +{ + assert(" foobar ".stripRight!(a => a == ' ')() == " foobar"); + assert("00223.444500".stripRight!(a => a == '0')() == "00223.4445"); + assert("ùniçodêéé".stripRight!(a => a == 'é')() == "ùniçodê"); + assert([1, 1, 0, 1, 1].stripRight!(a => a == 1)() == [1, 1, 0]); + assert([0.0, 0.01, 0.10, 0.5, 0.6].stripRight!(a => a > 0.4)().length == 3); +} + +// swap +/** +Swaps $(D lhs) and $(D rhs). The instances $(D lhs) and $(D rhs) are moved in +memory, without ever calling $(D opAssign), nor any other function. $(D T) +need not be assignable at all to be swapped. + +If $(D lhs) and $(D rhs) reference the same instance, then nothing is done. + +$(D lhs) and $(D rhs) must be mutable. If $(D T) is a struct or union, then +its fields must also all be (recursively) mutable. + +Params: + lhs = Data to be swapped with $(D rhs). + rhs = Data to be swapped with $(D lhs). +*/ +void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow @nogc +if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) +{ + import std.traits : hasAliasing, hasElaborateAssign, isAssignable, + isStaticArray; + static if (hasAliasing!T) if (!__ctfe) + { + import std.exception : doesPointTo; + assert(!doesPointTo(lhs, lhs), "Swap: lhs internal pointer."); + assert(!doesPointTo(rhs, rhs), "Swap: rhs internal pointer."); + assert(!doesPointTo(lhs, rhs), "Swap: lhs points to rhs."); + assert(!doesPointTo(rhs, lhs), "Swap: rhs points to lhs."); + } + + static if (hasElaborateAssign!T || !isAssignable!T) + { + if (&lhs != &rhs) + { + // For structs with non-trivial assignment, move memory directly + ubyte[T.sizeof] t = void; + auto a = (cast(ubyte*) &lhs)[0 .. T.sizeof]; + auto b = (cast(ubyte*) &rhs)[0 .. T.sizeof]; + t[] = a[]; + a[] = b[]; + b[] = t[]; + } + } + else + { + //Avoid assigning overlapping arrays. Dynamic arrays are fine, because + //it's their ptr and length properties which get assigned rather + //than their elements when assigning them, but static arrays are value + //types and therefore all of their elements get copied as part of + //assigning them, which would be assigning overlapping arrays if lhs + //and rhs were the same array. + static if (isStaticArray!T) + { + if (lhs.ptr == rhs.ptr) + return; + } + + // For non-struct types, suffice to do the classic swap + auto tmp = lhs; + lhs = rhs; + rhs = tmp; + } +} + +// Not yet documented +void swap(T)(ref T lhs, ref T rhs) if (is(typeof(lhs.proxySwap(rhs)))) +{ + lhs.proxySwap(rhs); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int a = 42, b = 34; + swap(a, b); + assert(a == 34 && b == 42); + + static struct S { int x; char c; int[] y; } + S s1 = { 0, 'z', [ 1, 2 ] }; + S s2 = { 42, 'a', [ 4, 6 ] }; + //writeln(s2.tupleof.stringof); + swap(s1, s2); + assert(s1.x == 42); + assert(s1.c == 'a'); + assert(s1.y == [ 4, 6 ]); + + assert(s2.x == 0); + assert(s2.c == 'z'); + assert(s2.y == [ 1, 2 ]); + + immutable int imm1, imm2; + static assert(!__traits(compiles, swap(imm1, imm2))); +} + +@safe unittest +{ + static struct NoCopy + { + this(this) { assert(0); } + int n; + string s; + } + NoCopy nc1, nc2; + nc1.n = 127; nc1.s = "abc"; + nc2.n = 513; nc2.s = "uvwxyz"; + swap(nc1, nc2); + assert(nc1.n == 513 && nc1.s == "uvwxyz"); + assert(nc2.n == 127 && nc2.s == "abc"); + swap(nc1, nc1); + swap(nc2, nc2); + assert(nc1.n == 513 && nc1.s == "uvwxyz"); + assert(nc2.n == 127 && nc2.s == "abc"); + + static struct NoCopyHolder + { + NoCopy noCopy; + } + NoCopyHolder h1, h2; + h1.noCopy.n = 31; h1.noCopy.s = "abc"; + h2.noCopy.n = 65; h2.noCopy.s = null; + swap(h1, h2); + assert(h1.noCopy.n == 65 && h1.noCopy.s == null); + assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); + swap(h1, h1); + swap(h2, h2); + assert(h1.noCopy.n == 65 && h1.noCopy.s == null); + assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); + + const NoCopy const1, const2; + static assert(!__traits(compiles, swap(const1, const2))); +} + +@safe unittest +{ + //Bug# 4789 + int[1] s = [1]; + swap(s, s); +} + +@safe unittest +{ + static struct NoAssign + { + int i; + void opAssign(NoAssign) @disable; + } + auto s1 = NoAssign(1); + auto s2 = NoAssign(2); + swap(s1, s2); + assert(s1.i == 2); + assert(s2.i == 1); +} + +@safe unittest +{ + struct S + { + const int i; + } + S s; + static assert(!__traits(compiles, swap(s, s))); +} + +@safe unittest +{ + //11853 + import std.traits : isAssignable; + alias T = Tuple!(int, double); + static assert(isAssignable!T); +} + +@safe unittest +{ + // 12024 + import std.datetime; + SysTime a, b; +} + +unittest // 9975 +{ + import std.exception : doesPointTo, mayPointTo; + static struct S2 + { + union + { + size_t sz; + string s; + } + } + S2 a , b; + a.sz = -1; + assert(!doesPointTo(a, b)); + assert( mayPointTo(a, b)); + swap(a, b); + + //Note: we can catch an error here, because there is no RAII in this test + import std.exception : assertThrown; + void* p, pp; + p = &p; + assertThrown!Error(move(p)); + assertThrown!Error(move(p, pp)); + assertThrown!Error(swap(p, pp)); +} + +unittest +{ + static struct A + { + int* x; + this(this) { x = new int; } + } + A a1, a2; + swap(a1, a2); + + static struct B + { + int* x; + void opAssign(B) { x = new int; } + } + B b1, b2; + swap(b1, b2); +} + +void swapFront(R1, R2)(R1 r1, R2 r2) + if (isInputRange!R1 && isInputRange!R2) +{ + static if (is(typeof(swap(r1.front, r2.front)))) + { + swap(r1.front, r2.front); + } + else + { + auto t1 = moveFront(r1), t2 = moveFront(r2); + r1.front = move(t2); + r2.front = move(t1); + } +} + +// swapRanges +/** +Swaps all elements of $(D r1) with successive elements in $(D r2). +Returns a tuple containing the remainder portions of $(D r1) and $(D +r2) that were not swapped (one of them will be empty). The ranges may +be of different types but must have the same element type and support +swapping. +*/ +Tuple!(Range1, Range2) +swapRanges(Range1, Range2)(Range1 r1, Range2 r2) + if (isInputRange!(Range1) && isInputRange!(Range2) + && hasSwappableElements!(Range1) && hasSwappableElements!(Range2) + && is(ElementType!(Range1) == ElementType!(Range2))) +{ + for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) + { + swap(r1.front, r2.front); + } + return tuple(r1, r2); +} + +/// +@safe unittest +{ + int[] a = [ 100, 101, 102, 103 ]; + int[] b = [ 0, 1, 2, 3 ]; + auto c = swapRanges(a[1 .. 3], b[2 .. 4]); + assert(c[0].empty && c[1].empty); + assert(a == [ 100, 2, 3, 103 ]); + assert(b == [ 0, 1, 101, 102 ]); +} + +/** +Initializes each element of $(D range) with $(D value). +Assumes that the elements of the range are uninitialized. +This is of interest for structs that +define copy constructors (for all other types, $(LREF fill) and +uninitializedFill are equivalent). + +Params: + range = An $(XREF2 range, isInputRange, input range) that exposes references to its elements + and has assignable elements + value = Assigned to each element of range + +See_Also: + $(LREF fill) + $(LREF initializeAll) + +Example: +---- +struct S { ... } +S[] s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5]; +uninitializedFill(s, 42); +assert(s == [ 42, 42, 42, 42, 42 ]); +---- + */ +void uninitializedFill(Range, Value)(Range range, Value value) + if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = value))) +{ + import std.traits : hasElaborateAssign; + + alias T = ElementType!Range; + static if (hasElaborateAssign!T) + { + import std.conv : emplaceRef; + + // Must construct stuff by the book + for (; !range.empty; range.popFront()) + emplaceRef!T(range.front, value); + } + else + // Doesn't matter whether fill is initialized or not + return fill(range, value); +} + diff --git a/std/algorithm/package.d b/std/algorithm/package.d new file mode 100644 index 00000000000..efd4563becc --- /dev/null +++ b/std/algorithm/package.d @@ -0,0 +1,375 @@ +// Written in the D programming language. + +/** +This package implements generic algorithms oriented towards the processing of +sequences. Sequences processed by these functions define range-based +interfaces. See also $(LINK2 std_range.html, Reference on ranges) and +$(WEB ddili.org/ders/d.en/ranges.html, tutorial on ranges). + +$(SCRIPT inhibitQuickIndex = 1;) + +Algorithms are categorized into the following submodules: + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Submodule) $(TH Functions) +) +$(TR $(TDNW Searching) + $(TDNW $(SUBMODULE searching)) + $(TD + $(SUBREF searching, all) + $(SUBREF searching, any) + $(SUBREF searching, balancedParens) + $(SUBREF searching, boyerMooreFinder) + $(SUBREF searching, canFind) + $(SUBREF searching, commonPrefix) + $(SUBREF searching, count) + $(SUBREF searching, countUntil) + $(SUBREF searching, endsWith) + $(SUBREF searching, find) + $(SUBREF searching, findAdjacent) + $(SUBREF searching, findAmong) + $(SUBREF searching, findSkip) + $(SUBREF searching, findSplit) + $(SUBREF searching, findSplitAfter) + $(SUBREF searching, findSplitBefore) + $(SUBREF searching, minCount) + $(SUBREF searching, minPos) + $(SUBREF searching, skipOver) + $(SUBREF searching, startsWith) + $(SUBREF searching, until) + ) +) +$(TR $(TDNW Comparison) + $(TDNW $(SUBMODULE comparison)) + $(TD + $(SUBREF comparison, among) + $(SUBREF comparison, castSwitch) + $(SUBREF comparison, clamp) + $(SUBREF comparison, cmp) + $(SUBREF comparison, equal) + $(SUBREF comparison, levenshteinDistance) + $(SUBREF comparison, levenshteinDistanceAndPath) + $(SUBREF comparison, max) + $(SUBREF comparison, min) + $(SUBREF comparison, mismatch) + $(SUBREF comparison, predSwitch) + ) +) +$(TR $(TDNW Iteration) + $(TDNW $(SUBMODULE iteration)) + $(TD + $(SUBREF iteration, aggregate) + $(SUBREF iteration, cache) + $(SUBREF iteration, cacheBidirectional) + $(SUBREF iteration, each) + $(SUBREF iteration, filter) + $(SUBREF iteration, filterBidirectional) + $(SUBREF iteration, group) + $(SUBREF iteration, groupBy) + $(SUBREF iteration, joiner) + $(SUBREF iteration, map) + $(SUBREF iteration, reduce) + $(SUBREF iteration, splitter) + $(SUBREF iteration, sum) + $(SUBREF iteration, uniq) + ) +) +$(TR $(TDNW Sorting) + $(TDNW $(SUBMODULE sorting)) + $(TD + $(SUBREF sorting, completeSort) + $(SUBREF sorting, isPartitioned) + $(SUBREF sorting, isSorted) + $(SUBREF sorting, makeIndex) + $(SUBREF sorting, multiSort) + $(SUBREF sorting, nextEvenPermutation) + $(SUBREF sorting, nextPermutation) + $(SUBREF sorting, partialSort) + $(SUBREF sorting, partition) + $(SUBREF sorting, partition3) + $(SUBREF sorting, schwartzSort) + $(SUBREF sorting, sort) + $(SUBREF sorting, topN) + $(SUBREF sorting, topNCopy) + $(SUBREF sorting, topNIndex) + ) +) +$(TR $(TDNW Set operations) + $(TDNW $(SUBMODULE setops)) + $(TD + $(SUBREF setops, cartesianProduct) + $(SUBREF setops, largestPartialIntersection) + $(SUBREF setops, largestPartialIntersectionWeighted) + $(SUBREF setops, nWayUnion) + $(SUBREF setops, setDifference) + $(SUBREF setops, setIntersection) + $(SUBREF setops, setSymmetricDifference) + $(SUBREF setops, setUnion) + ) +) +$(TR $(TDNW Mutation) + $(TDNW $(SUBMODULE mutation)) + $(TD + $(SUBREF mutation, bringToFront) + $(SUBREF mutation, copy) + $(SUBREF mutation, fill) + $(SUBREF mutation, initializeAll) + $(SUBREF mutation, move) + $(SUBREF mutation, moveAll) + $(SUBREF mutation, moveSome) + $(SUBREF mutation, remove) + $(SUBREF mutation, reverse) + $(SUBREF mutation, strip) + $(SUBREF mutation, stripLeft) + $(SUBREF mutation, stripRight) + $(SUBREF mutation, swap) + $(SUBREF mutation, swapRanges) + $(SUBREF mutation, uninitializedFill) + ) +) +$(TR $(TDNW Utility) + $(TDNW -) + $(TD $(MYREF forward) + ) +)) +) + +Many functions in this package are parameterized with a function or a +$(GLOSSARY predicate). The predicate may be passed either as a +function name, a delegate name, a $(GLOSSARY functor) name, or a +compile-time string. The string may consist of $(B any) legal D +expression that uses the symbol $(D a) (for unary functions) or the +symbols $(D a) and $(D b) (for binary functions). These names will NOT +interfere with other homonym symbols in user code because they are +evaluated in a different context. The default for all binary +comparison predicates is $(D "a == b") for unordered operations and +$(D "a < b") for ordered operations. + +Example: + +---- +int[] a = ...; +static bool greater(int a, int b) +{ + return a > b; +} +sort!(greater)(a); // predicate as alias +sort!("a > b")(a); // predicate as string + // (no ambiguity with array name) +sort(a); // no predicate, "a < b" is implicit +---- + +Macros: +WIKI = Phobos/StdAlgorithm +SUBMODULE = $(LINK2 std_algorithm_$1.html, std.algorithm.$1) +SUBREF = $(LINK2 std_algorithm_$1.html#.$2, $(TT $2))$(NBSP) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/_algorithm/package.d) + */ +module std.algorithm; +//debug = std_algorithm; + +public import std.algorithm.comparison; +public import std.algorithm.iteration; +public import std.algorithm.mutation; +public import std.algorithm.setops; +public import std.algorithm.searching; +public import std.algorithm.sorting; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +// FIXME +import std.range; // : SortedRange; +import std.traits; +// FIXME +import std.typecons; // : tuple, Tuple; +// FIXME +import std.typetuple; // : TypeTuple, staticMap, allSatisfy, anySatisfy; + +version(unittest) debug(std_algorithm) import std.stdio; + +package T* addressOf(T)(ref T val) { return &val; } + +// Same as std.string.format, but "self-importing". +// Helps reduce code and imports, particularly in static asserts. +// Also helps with missing imports errors. +package template algoFormat() +{ + import std.format : format; + alias algoFormat = format; +} + +/** +Forwards function arguments with saving ref-ness. +*/ +template forward(args...) +{ + import std.typetuple; + + static if (args.length) + { + alias arg = args[0]; + static if (__traits(isRef, arg)) + alias fwd = arg; + else + @property fwd()(){ return move(arg); } + alias forward = TypeTuple!(fwd, forward!(args[1..$])); + } + else + alias forward = TypeTuple!(); +} + +/// +@safe unittest +{ + class C + { + static int foo(int n) { return 1; } + static int foo(ref int n) { return 2; } + } + int bar()(auto ref int x) { return C.foo(forward!x); } + + assert(bar(1) == 1); + int i; + assert(bar(i) == 2); +} + +/// +@safe unittest +{ + void foo(int n, ref string s) { s = null; foreach (i; 0..n) s ~= "Hello"; } + + // forwards all arguments which are bound to parameter tuple + void bar(Args...)(auto ref Args args) { return foo(forward!args); } + + // forwards all arguments with swapping order + void baz(Args...)(auto ref Args args) { return foo(forward!args[$/2..$], forward!args[0..$/2]); } + + string s; + bar(1, s); + assert(s == "Hello"); + baz(s, 2); + assert(s == "HelloHello"); +} + +@safe unittest +{ + auto foo(TL...)(auto ref TL args) + { + string result = ""; + foreach (i, _; args) + { + //pragma(msg, "[",i,"] ", __traits(isRef, args[i]) ? "L" : "R"); + result ~= __traits(isRef, args[i]) ? "L" : "R"; + } + return result; + } + + string bar(TL...)(auto ref TL args) + { + return foo(forward!args); + } + string baz(TL...)(auto ref TL args) + { + int x; + return foo(forward!args[3], forward!args[2], 1, forward!args[1], forward!args[0], x); + } + + struct S {} + S makeS(){ return S(); } + int n; + string s; + assert(bar(S(), makeS(), n, s) == "RRLL"); + assert(baz(S(), makeS(), n, s) == "LLRRRL"); +} + +@safe unittest +{ + ref int foo(return ref int a) { return a; } + ref int bar(Args)(auto ref Args args) + { + return foo(forward!args); + } + static assert(!__traits(compiles, { auto x1 = bar(3); })); // case of NG + int value = 3; + auto x2 = bar(value); // case of OK +} + +/** +Specifies whether the output of certain algorithm is desired in sorted +format. + */ +enum SortOutput +{ + no, /// Don't sort output + yes, /// Sort output +} + +// Internal random array generators +version(unittest) +{ + package enum size_t maxArraySize = 50; + package enum size_t minArraySize = maxArraySize - 1; + + package string[] rndstuff(T : string)() + { + import std.random : Random, unpredictableSeed, uniform; + + static Random rnd; + static bool first = true; + if (first) + { + rnd = Random(unpredictableSeed); + first = false; + } + string[] result = + new string[uniform(minArraySize, maxArraySize, rnd)]; + string alpha = "abcdefghijABCDEFGHIJ"; + foreach (ref s; result) + { + foreach (i; 0 .. uniform(0u, 20u, rnd)) + { + auto j = uniform(0, alpha.length - 1, rnd); + s ~= alpha[j]; + } + } + return result; + } + + package int[] rndstuff(T : int)() + { + import std.random : Random, unpredictableSeed, uniform; + + static Random rnd; + static bool first = true; + if (first) + { + rnd = Random(unpredictableSeed); + first = false; + } + int[] result = new int[uniform(minArraySize, maxArraySize, rnd)]; + foreach (ref i; result) + { + i = uniform(-100, 100, rnd); + } + return result; + } + + package double[] rndstuff(T : double)() + { + double[] result; + foreach (i; rndstuff!(int)()) + { + result ~= i / 50.0; + } + return result; + } +} diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d new file mode 100644 index 00000000000..903c2e03ee0 --- /dev/null +++ b/std/algorithm/searching.d @@ -0,0 +1,3437 @@ +// Written in the D programming language. +/** +This is a submodule of $(LINK2 std_algorithm_package.html, std.algorithm). +It contains generic _searching algorithms. + +$(BOOKTABLE Cheat Sheet, + +$(TR $(TH Function Name) $(TH Description)) + +$(T2 all, + $(D all!"a > 0"([1, 2, 3, 4])) returns $(D true) because all elements + are positive) +$(T2 any, + $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one + element is positive) +$(T2 balancedParens, + $(D balancedParens("((1 + 1) / 2)")) returns $(D true) because the + string has balanced parentheses.) +$(T2 boyerMooreFinder, + $(D find("hello world", boyerMooreFinder("or"))) returns $(D "orld") + using the $(LUCKY Boyer-Moore _algorithm).) +$(T2 canFind, + $(D canFind("hello world", "or")) returns $(D true).) +$(T2 count, + Counts elements that are equal to a specified value or satisfy a + predicate. $(D count([1, 2, 1], 1)) returns $(D 2) and + $(D count!"a < 0"([1, -3, 0])) returns $(D 1).) +$(T2 countUntil, + $(D countUntil(a, b)) returns the number of steps taken in $(D a) to + reach $(D b); for example, $(D countUntil("hello!", "o")) returns + $(D 4).) +$(T2 commonPrefix, + $(D commonPrefix("parakeet", "parachute")) returns $(D "para").) +$(T2 endsWith, + $(D endsWith("rocks", "ks")) returns $(D true).) +$(T2 find, + $(D find("hello world", "or")) returns $(D "orld") using linear search. + (For binary search refer to $(XREF range,sortedRange).)) +$(T2 findAdjacent, + $(D findAdjacent([1, 2, 3, 3, 4])) returns the subrange starting with + two equal adjacent elements, i.e. $(D [3, 3, 4]).) +$(T2 findAmong, + $(D findAmong("abcd", "qcx")) returns $(D "cd") because $(D 'c') is + among $(D "qcx").) +$(T2 findSkip, + If $(D a = "abcde"), then $(D findSkip(a, "x")) returns $(D false) and + leaves $(D a) unchanged, whereas $(D findSkip(a, 'c')) advances $(D a) + to $(D "cde") and returns $(D true).) +$(T2 findSplit, + $(D findSplit("abcdefg", "de")) returns the three ranges $(D "abc"), + $(D "de"), and $(D "fg").) +$(T2 findSplitAfter, + $(D findSplitAfter("abcdefg", "de")) returns the two ranges + $(D "abcde") and $(D "fg").) +$(T2 findSplitBefore, + $(D findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc") + and $(D "defg").) +$(T2 minCount, + $(D minCount([2, 1, 1, 4, 1])) returns $(D tuple(1, 3)).) +$(T2 minPos, + $(D minPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [1, 3, 4, 1]), + i.e., positions the range at the first occurrence of its minimal + element.) +$(T2 mismatch, + $(D mismatch("parakeet", "parachute")) returns the two ranges + $(D "keet") and $(D "chute").) +$(T2 skipOver, + Assume $(D a = "blah"). Then $(D skipOver(a, "bi")) leaves $(D a) + unchanged and returns $(D false), whereas $(D skipOver(a, "bl")) + advances $(D a) to refer to $(D "ah") and returns $(D true).) +$(T2 startsWith, + $(D startsWith("hello, world", "hello")) returns $(D true).) +$(T2 until, + Lazily iterates a range until a specific value is found.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_searching.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.searching; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +import std.traits; +// FIXME +import std.typecons; // : Tuple; + +/++ +Checks if $(I _all) of the elements verify $(D pred). + +/ +template all(alias pred = "a") +{ + /++ + Returns $(D true) if and only if $(I _all) values $(D v) found in the + input range $(D range) satisfy the predicate $(D pred). + Performs (at most) $(BIGOH range.length) evaluations of $(D pred). + +/ + bool all(Range)(Range range) + if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) + { + import std.functional : not; + + return find!(not!(unaryFun!pred))(range).empty; + } +} + +/// +@safe unittest +{ + assert( all!"a & 1"([1, 3, 5, 7, 9])); + assert(!all!"a & 1"([1, 2, 3, 5, 7, 9])); +} + +/++ +$(D all) can also be used without a predicate, if its items can be +evaluated to true or false in a conditional statement. This can be a +convenient way to quickly evaluate that $(I _all) of the elements of a range +are true. + +/ +@safe unittest +{ + int[3] vals = [5, 3, 18]; + assert( all(vals[])); +} + +@safe unittest +{ + int x = 1; + assert(all!(a => a > x)([2, 3])); +} + +/++ +Checks if $(I _any) of the elements verifies $(D pred). +$(D !any) can be used to verify that $(I none) of the elements verify +$(D pred). + +/ +template any(alias pred = "a") +{ + /++ + Returns $(D true) if and only if $(I _any) value $(D v) found in the + input range $(D range) satisfies the predicate $(D pred). + Performs (at most) $(BIGOH range.length) evaluations of $(D pred). + +/ + bool any(Range)(Range range) + if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) + { + return !find!pred(range).empty; + } +} + +/// +@safe unittest +{ + import std.ascii : isWhite; + assert( all!(any!isWhite)(["a a", "b b"])); + assert(!any!(all!isWhite)(["a a", "b b"])); +} + +/++ +$(D any) can also be used without a predicate, if its items can be +evaluated to true or false in a conditional statement. $(D !any) can be a +convenient way to quickly test that $(I none) of the elements of a range +evaluate to true. + +/ +@safe unittest +{ + int[3] vals1 = [0, 0, 0]; + assert(!any(vals1[])); //none of vals1 evaluate to true + + int[3] vals2 = [2, 0, 2]; + assert( any(vals2[])); + assert(!all(vals2[])); + + int[3] vals3 = [3, 3, 3]; + assert( any(vals3[])); + assert( all(vals3[])); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto a = [ 1, 2, 0, 4 ]; + assert(any!"a == 2"(a)); +} + +// balancedParens +/** +Checks whether $(D r) has "balanced parentheses", i.e. all instances +of $(D lPar) are closed by corresponding instances of $(D rPar). The +parameter $(D maxNestingLevel) controls the nesting level allowed. The +most common uses are the default or $(D 0). In the latter case, no +nesting is allowed. +*/ +bool balancedParens(Range, E)(Range r, E lPar, E rPar, + size_t maxNestingLevel = size_t.max) +if (isInputRange!(Range) && is(typeof(r.front == lPar))) +{ + size_t count; + for (; !r.empty; r.popFront()) + { + if (r.front == lPar) + { + if (count > maxNestingLevel) return false; + ++count; + } + else if (r.front == rPar) + { + if (!count) return false; + --count; + } + } + return count == 0; +} + +/// +@safe unittest +{ + auto s = "1 + (2 * (3 + 1 / 2)"; + assert(!balancedParens(s, '(', ')')); + s = "1 + (2 * (3 + 1) / 2)"; + assert(balancedParens(s, '(', ')')); + s = "1 + (2 * (3 + 1) / 2)"; + assert(!balancedParens(s, '(', ')', 0)); + s = "1 + (2 * 3 + 1) / (2 - 5)"; + assert(balancedParens(s, '(', ')', 0)); +} + +/** + * Sets up Boyer-Moore matching for use with $(D find) below. + * By default, elements are compared for equality. + * + * $(D BoyerMooreFinder) allocates GC memory. + * + * Params: + * pred = Predicate used to compare elements. + * needle = A random-access range with length and slicing. + * + * Returns: + * An instance of $(D BoyerMooreFinder) that can be used with $(D find()) to + * invoke the Boyer-Moore matching algorithm for finding of $(D needle) in a + * given haystack. + */ +BoyerMooreFinder!(binaryFun!(pred), Range) boyerMooreFinder +(alias pred = "a == b", Range) +(Range needle) if (isRandomAccessRange!(Range) || isSomeString!Range) +{ + return typeof(return)(needle); +} + +/// Ditto +struct BoyerMooreFinder(alias pred, Range) +{ +private: + size_t[] skip; // GC allocated + ptrdiff_t[ElementType!(Range)] occ; // GC allocated + Range needle; + + ptrdiff_t occurrence(ElementType!(Range) c) + { + auto p = c in occ; + return p ? *p : -1; + } + +/* +This helper function checks whether the last "portion" bytes of +"needle" (which is "nlen" bytes long) exist within the "needle" at +offset "offset" (counted from the end of the string), and whether the +character preceding "offset" is not a match. Notice that the range +being checked may reach beyond the beginning of the string. Such range +is ignored. + */ + static bool needlematch(R)(R needle, + size_t portion, size_t offset) + { + import std.algorithm.comparison : equal; + ptrdiff_t virtual_begin = needle.length - offset - portion; + ptrdiff_t ignore = 0; + if (virtual_begin < 0) { + ignore = -virtual_begin; + virtual_begin = 0; + } + if (virtual_begin > 0 + && needle[virtual_begin - 1] == needle[$ - portion - 1]) + return 0; + + immutable delta = portion - ignore; + return equal(needle[needle.length - delta .. needle.length], + needle[virtual_begin .. virtual_begin + delta]); + } + +public: + this(Range needle) + { + if (!needle.length) return; + this.needle = needle; + /* Populate table with the analysis of the needle */ + /* But ignoring the last letter */ + foreach (i, n ; needle[0 .. $ - 1]) + { + this.occ[n] = i; + } + /* Preprocess #2: init skip[] */ + /* Note: This step could be made a lot faster. + * A simple implementation is shown here. */ + this.skip = new size_t[needle.length]; + foreach (a; 0 .. needle.length) + { + size_t value = 0; + while (value < needle.length + && !needlematch(needle, a, value)) + { + ++value; + } + this.skip[needle.length - a - 1] = value; + } + } + + Range beFound(Range haystack) + { + import std.algorithm.comparison : max; + + if (!needle.length) return haystack; + if (needle.length > haystack.length) return haystack[$ .. $]; + /* Search: */ + auto limit = haystack.length - needle.length; + for (size_t hpos = 0; hpos <= limit; ) + { + size_t npos = needle.length - 1; + while (pred(needle[npos], haystack[npos+hpos])) + { + if (npos == 0) return haystack[hpos .. $]; + --npos; + } + hpos += max(skip[npos], cast(sizediff_t) npos - occurrence(haystack[npos+hpos])); + } + return haystack[$ .. $]; + } + + @property size_t length() + { + return needle.length; + } + + alias opDollar = length; +} + +/** +Returns the common prefix of two ranges. + +Params: + pred = The predicate to use in comparing elements for commonality. Defaults + to equality $(D "a == b"). + + r1 = A $(XREF2 range, isForwardRange, forward range) of elements. + + r2 = An $(XREF2 range, isInputRange, input range) of elements. + +Returns: +A slice of $(D r1) which contains the characters that both ranges start with, +if the first argument is a string; otherwise, the same as the result of +$(D takeExactly(r1, n)), where $(D n) is the number of elements in the common +prefix of both ranges. + +See_Also: + $(XREF range, takeExactly) + */ +auto commonPrefix(alias pred = "a == b", R1, R2)(R1 r1, R2 r2) +if (isForwardRange!R1 && isInputRange!R2 && + !isNarrowString!R1 && + is(typeof(binaryFun!pred(r1.front, r2.front)))) +{ + import std.algorithm.comparison : min; + static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && + hasLength!R1 && hasLength!R2 && + hasSlicing!R1) + { + immutable limit = min(r1.length, r2.length); + foreach (i; 0 .. limit) + { + if (!binaryFun!pred(r1[i], r2[i])) + { + return r1[0 .. i]; + } + } + return r1[0 .. limit]; + } + else + { + import std.range : takeExactly; + auto result = r1.save; + size_t i = 0; + for (; + !r1.empty && !r2.empty && binaryFun!pred(r1.front, r2.front); + ++i, r1.popFront(), r2.popFront()) + {} + return takeExactly(result, i); + } +} + +/// +@safe unittest +{ + assert(commonPrefix("hello, world", "hello, there") == "hello, "); +} + +/// ditto +auto commonPrefix(alias pred, R1, R2)(R1 r1, R2 r2) +if (isNarrowString!R1 && isInputRange!R2 && + is(typeof(binaryFun!pred(r1.front, r2.front)))) +{ + import std.utf : decode; + + auto result = r1.save; + immutable len = r1.length; + size_t i = 0; + + for (size_t j = 0; i < len && !r2.empty; r2.popFront(), i = j) + { + immutable f = decode(r1, j); + if (!binaryFun!pred(f, r2.front)) + break; + } + + return result[0 .. i]; +} + +/// ditto +auto commonPrefix(R1, R2)(R1 r1, R2 r2) +if (isNarrowString!R1 && isInputRange!R2 && !isNarrowString!R2 && + is(typeof(r1.front == r2.front))) +{ + return commonPrefix!"a == b"(r1, r2); +} + +/// ditto +auto commonPrefix(R1, R2)(R1 r1, R2 r2) +if (isNarrowString!R1 && isNarrowString!R2) +{ + import std.algorithm.comparison : min; + + static if (ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof) + { + import std.utf : stride, UTFException; + + immutable limit = min(r1.length, r2.length); + for (size_t i = 0; i < limit;) + { + immutable codeLen = std.utf.stride(r1, i); + size_t j = 0; + + for (; j < codeLen && i < limit; ++i, ++j) + { + if (r1[i] != r2[i]) + return r1[0 .. i - j]; + } + + if (i == limit && j < codeLen) + throw new UTFException("Invalid UTF-8 sequence", i); + } + return r1[0 .. limit]; + } + else + return commonPrefix!"a == b"(r1, r2); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + import std.conv : to; + import std.exception : assertThrown; + import std.utf : UTFException; + import std.range; + import std.typetuple : TypeTuple; + + assert(commonPrefix([1, 2, 3], [1, 2, 3, 4, 5]) == [1, 2, 3]); + assert(commonPrefix([1, 2, 3, 4, 5], [1, 2, 3]) == [1, 2, 3]); + assert(commonPrefix([1, 2, 3, 4], [1, 2, 3, 4]) == [1, 2, 3, 4]); + assert(commonPrefix([1, 2, 3], [7, 2, 3, 4, 5]).empty); + assert(commonPrefix([7, 2, 3, 4, 5], [1, 2, 3]).empty); + assert(commonPrefix([1, 2, 3], cast(int[])null).empty); + assert(commonPrefix(cast(int[])null, [1, 2, 3]).empty); + assert(commonPrefix(cast(int[])null, cast(int[])null).empty); + + foreach (S; TypeTuple!(char[], const(char)[], string, + wchar[], const(wchar)[], wstring, + dchar[], const(dchar)[], dstring)) + { + foreach(T; TypeTuple!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(commonPrefix(to!S(""), to!T("")).empty); + assert(commonPrefix(to!S(""), to!T("hello")).empty); + assert(commonPrefix(to!S("hello"), to!T("")).empty); + assert(commonPrefix(to!S("hello, world"), to!T("hello, there")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, there"), to!T("hello, world")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, "), to!T("hello, world")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, world"), to!T("hello, ")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, world"), to!T("hello, world")) == to!S("hello, world")); + + //Bug# 8890 + assert(commonPrefix(to!S("Пиво"), to!T("Пони"))== to!S("П")); + assert(commonPrefix(to!S("Пони"), to!T("Пиво"))== to!S("П")); + assert(commonPrefix(to!S("Пиво"), to!T("Пиво"))== to!S("Пиво")); + assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFE"), + to!T("\U0010FFFF\U0010FFFB\U0010FFFC")) == to!S("\U0010FFFF\U0010FFFB")); + assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFC"), + to!T("\U0010FFFF\U0010FFFB\U0010FFFE")) == to!S("\U0010FFFF\U0010FFFB")); + assert(commonPrefix!"a != b"(to!S("Пиво"), to!T("онво")) == to!S("Пи")); + assert(commonPrefix!"a != b"(to!S("онво"), to!T("Пиво")) == to!S("он")); + }(); + + static assert(is(typeof(commonPrefix(to!S("Пиво"), filter!"true"("Пони"))) == S)); + assert(equal(commonPrefix(to!S("Пиво"), filter!"true"("Пони")), to!S("П"))); + + static assert(is(typeof(commonPrefix(filter!"true"("Пиво"), to!S("Пони"))) == + typeof(takeExactly(filter!"true"("П"), 1)))); + assert(equal(commonPrefix(filter!"true"("Пиво"), to!S("Пони")), takeExactly(filter!"true"("П"), 1))); + } + + assertThrown!UTFException(commonPrefix("\U0010FFFF\U0010FFFB", "\U0010FFFF\U0010FFFB"[0 .. $ - 1])); + + assert(commonPrefix("12345"d, [49, 50, 51, 60, 60]) == "123"d); + assert(commonPrefix([49, 50, 51, 60, 60], "12345" ) == [49, 50, 51]); + assert(commonPrefix([49, 50, 51, 60, 60], "12345"d) == [49, 50, 51]); + + assert(commonPrefix!"a == ('0' + b)"("12345" , [1, 2, 3, 9, 9]) == "123"); + assert(commonPrefix!"a == ('0' + b)"("12345"d, [1, 2, 3, 9, 9]) == "123"d); + assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345" ) == [1, 2, 3]); + assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345"d) == [1, 2, 3]); +} + +// count +/** +The first version counts the number of elements $(D x) in $(D r) for +which $(D pred(x, value)) is $(D true). $(D pred) defaults to +equality. Performs $(BIGOH haystack.length) evaluations of $(D pred). + +The second version returns the number of times $(D needle) occurs in +$(D haystack). Throws an exception if $(D needle.empty), as the _count +of the empty range in any range would be infinite. Overlapped counts +are not considered, for example $(D count("aaa", "aa")) is $(D 1), not +$(D 2). + +The third version counts the elements for which $(D pred(x)) is $(D +true). Performs $(BIGOH haystack.length) evaluations of $(D pred). + +Note: Regardless of the overload, $(D count) will not accept +infinite ranges for $(D haystack). +*/ +size_t count(alias pred = "a == b", Range, E)(Range haystack, E needle) + if (isInputRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) +{ + bool pred2(ElementType!Range a) { return binaryFun!pred(a, needle); } + return count!pred2(haystack); +} + +/// +@safe unittest +{ + import std.uni : toLower; + + // count elements in range + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count(a, 2) == 3); + assert(count!("a > b")(a, 2) == 5); + // count range in range + assert(count("abcadfabf", "ab") == 2); + assert(count("ababab", "abab") == 1); + assert(count("ababab", "abx") == 0); + // fuzzy count range in range + assert(count!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))("AbcAdFaBf", "ab") == 2); + // count predicate in range + assert(count!("a > 1")(a) == 8); +} + +@safe unittest +{ + import std.conv : text; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count(a, 2) == 3, text(count(a, 2))); + assert(count!("a > b")(a, 2) == 5, text(count!("a > b")(a, 2))); + + // check strings + assert(count("日本語") == 3); + assert(count("日本語"w) == 3); + assert(count("日本語"d) == 3); + + assert(count!("a == '日'")("日本語") == 1); + assert(count!("a == '本'")("日本語"w) == 1); + assert(count!("a == '語'")("日本語"d) == 1); +} + +@safe unittest +{ + debug(std_algorithm) printf("algorithm.count.unittest\n"); + string s = "This is a fofofof list"; + string sub = "fof"; + assert(count(s, sub) == 2); +} + +/// Ditto +size_t count(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) + if (isForwardRange!R1 && !isInfinite!R1 && + isForwardRange!R2 && + is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) +{ + assert(!needle.empty, "Cannot count occurrences of an empty range"); + + static if (isInfinite!R2) + { + //Note: This is the special case of looking for an infinite inside a finite... + //"How many instances of the Fibonacci sequence can you count in [1, 2, 3]?" - "None." + return 0; + } + else + { + size_t result; + //Note: haystack is not saved, because findskip is designed to modify it + for ( ; findSkip!pred(haystack, needle.save) ; ++result) + {} + return result; + } +} + +/// Ditto +size_t count(alias pred = "true", R)(R haystack) + if (isInputRange!R && !isInfinite!R && + is(typeof(unaryFun!pred(haystack.front)) : bool)) +{ + size_t result; + alias T = ElementType!R; //For narrow strings forces dchar iteration + foreach (T elem; haystack) + if (unaryFun!pred(elem)) ++result; + return result; +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count!("a == 3")(a) == 2); + assert(count("日本語") == 3); +} + +// Issue 11253 +@safe nothrow unittest +{ + assert([1, 2, 3].count([2, 3]) == 1); +} + +/++ + Counts elements in the given $(XREF2 range, isForwardRange, forward range) + until the given predicate is true for one of the given $(D needles). + + Params: + pred = The predicate for determining when to stop counting. + haystack = The $(XREF2 range, isInputRange, input range) to be counted. + needles = Either a single element, or a $(XREF2 range, isForwardRange, + forward range) of elements, to be evaluated in turn against each + element in $(D haystack) under the given predicate. + + Returns: The number of elements which must be popped from the front of + $(D haystack) before reaching an element for which + $(D startsWith!pred(haystack, needles)) is $(D true). If + $(D startsWith!pred(haystack, needles)) is not $(D true) for any element in + $(D haystack), then $(D -1) is returned. + +/ +ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) + if (isForwardRange!R + && Rs.length > 0 + && isForwardRange!(Rs[0]) == isInputRange!(Rs[0]) + && is(typeof(startsWith!pred(haystack, needles[0]))) + && (Rs.length == 1 + || is(typeof(countUntil!pred(haystack, needles[1 .. $]))))) +{ + typeof(return) result; + + static if (needles.length == 1) + { + static if (hasLength!R) //Note: Narrow strings don't have length. + { + //We delegate to find because find is very efficient. + //We store the length of the haystack so we don't have to save it. + auto len = haystack.length; + auto r2 = find!pred(haystack, needles[0]); + if (!r2.empty) + return cast(typeof(return)) (len - r2.length); + } + else + { + import std.range : dropOne; + + if (needles[0].empty) + return 0; + + //Default case, slower route doing startsWith iteration + for ( ; !haystack.empty ; ++result ) + { + //We compare the first elements of the ranges here before + //forwarding to startsWith. This avoids making useless saves to + //haystack/needle if they aren't even going to be mutated anyways. + //It also cuts down on the amount of pops on haystack. + if (binaryFun!pred(haystack.front, needles[0].front)) + { + //Here, we need to save the needle before popping it. + //haystack we pop in all paths, so we do that, and then save. + haystack.popFront(); + if (startsWith!pred(haystack.save, needles[0].save.dropOne())) + return result; + } + else + haystack.popFront(); + } + } + } + else + { + foreach (i, Ri; Rs) + { + static if (isForwardRange!Ri) + { + if (needles[i].empty) + return 0; + } + } + Tuple!Rs t; + foreach (i, Ri; Rs) + { + static if (!isForwardRange!Ri) + { + t[i] = needles[i]; + } + } + for (; !haystack.empty ; ++result, haystack.popFront()) + { + foreach (i, Ri; Rs) + { + static if (isForwardRange!Ri) + { + t[i] = needles[i].save; + } + } + if (startsWith!pred(haystack.save, t.expand)) + { + return result; + } + } + } + + //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(0); + else return -1; +} + +/// ditto +ptrdiff_t countUntil(alias pred = "a == b", R, N)(R haystack, N needle) + if (isInputRange!R && + is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) +{ + bool pred2(ElementType!R a) { return binaryFun!pred(a, needle); } + return countUntil!pred2(haystack); +} + +/// +@safe unittest +{ + assert(countUntil("hello world", "world") == 6); + assert(countUntil("hello world", 'r') == 8); + assert(countUntil("hello world", "programming") == -1); + assert(countUntil("日本語", "本語") == 1); + assert(countUntil("日本語", '語') == 2); + assert(countUntil("日本語", "五") == -1); + assert(countUntil("日本語", '五') == -1); + assert(countUntil([0, 7, 12, 22, 9], [12, 22]) == 2); + assert(countUntil([0, 7, 12, 22, 9], 9) == 4); + assert(countUntil!"a > b"([0, 7, 12, 22, 9], 20) == 3); +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.internal.test.dummyrange; + + assert(countUntil("日本語", "") == 0); + assert(countUntil("日本語"d, "") == 0); + + assert(countUntil("", "") == 0); + assert(countUntil("".filter!"true"(), "") == 0); + + auto rf = [0, 20, 12, 22, 9].filter!"true"(); + assert(rf.countUntil!"a > b"((int[]).init) == 0); + assert(rf.countUntil!"a > b"(20) == 3); + assert(rf.countUntil!"a > b"([20, 8]) == 3); + assert(rf.countUntil!"a > b"([20, 10]) == -1); + assert(rf.countUntil!"a > b"([20, 8, 0]) == -1); + + auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]); + auto r2 = new ReferenceForwardRange!int([3, 4]); + auto r3 = new ReferenceForwardRange!int([3, 5]); + assert(r.save.countUntil(3) == 3); + assert(r.save.countUntil(r2) == 3); + assert(r.save.countUntil(7) == -1); + assert(r.save.countUntil(r3) == -1); +} + +@safe unittest +{ + assert(countUntil("hello world", "world", "asd") == 6); + assert(countUntil("hello world", "world", "ello") == 1); + assert(countUntil("hello world", "world", "") == 0); + assert(countUntil("hello world", "world", 'l') == 2); +} + +/++ + Similar to the previous overload of $(D countUntil), except that this one + evaluates only the predicate $(D pred). + + Params: + pred = Predicate to when to stop counting. + haystack = An $(XREF2 range, isInputRange, input range) of elements + to be counted. + Returns: The number of elements which must be popped from $(D haystack) + before $(D pred(haystack.front)) is $(D true). + +/ +ptrdiff_t countUntil(alias pred, R)(R haystack) + if (isInputRange!R && + is(typeof(unaryFun!pred(haystack.front)) : bool)) +{ + typeof(return) i; + static if (isRandomAccessRange!R) + { + //Optimized RA implementation. Since we want to count *and* iterate at + //the same time, it is more efficient this way. + static if (hasLength!R) + { + immutable len = cast(typeof(return)) haystack.length; + for ( ; i < len ; ++i ) + if (unaryFun!pred(haystack[i])) return i; + } + else //if (isInfinite!R) + { + for ( ; ; ++i ) + if (unaryFun!pred(haystack[i])) return i; + } + } + else static if (hasLength!R) + { + //For those odd ranges that have a length, but aren't RA. + //It is faster to quick find, and then compare the lengths + auto r2 = find!pred(haystack.save); + if (!r2.empty) return cast(typeof(return)) (haystack.length - r2.length); + } + else //Everything else + { + alias T = ElementType!R; //For narrow strings forces dchar iteration + foreach (T elem; haystack) + { + if (unaryFun!pred(elem)) return i; + ++i; + } + } + + //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(0); + else return -1; +} + +/// +@safe unittest +{ + import std.ascii : isDigit; + import std.uni : isWhite; + + assert(countUntil!(std.uni.isWhite)("hello world") == 5); + assert(countUntil!(std.ascii.isDigit)("hello world") == -1); + assert(countUntil!"a > 20"([0, 7, 12, 22, 9]) == 3); +} + +@safe unittest +{ + import std.internal.test.dummyrange; + + // References + { + // input + ReferenceInputRange!int r; + r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]); + assert(r.countUntil(3) == 3); + r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]); + assert(r.countUntil(7) == -1); + } + { + // forward + auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]); + assert(r.save.countUntil([3, 4]) == 3); + assert(r.save.countUntil(3) == 3); + assert(r.save.countUntil([3, 7]) == -1); + assert(r.save.countUntil(7) == -1); + } + { + // infinite forward + auto r = new ReferenceInfiniteForwardRange!int(0); + assert(r.save.countUntil([3, 4]) == 3); + assert(r.save.countUntil(3) == 3); + } +} + +/** +Checks if the given range ends with (one of) the given needle(s). +The reciprocal of $(D startsWith). + +Params: + pred = The predicate to use for comparing elements between the range and + the needle(s). + + doesThisEnd = The $(XREF2 range, isBidirectionalRange, bidirectional range) + to check. + + withOneOfThese = The needles to check against, which may be single + elements, or bidirectional ranges of elements. + + withThis = The single element to check. + +Returns: +0 if the needle(s) do not occur at the end of the given range; +otherwise the position of the matching needle, that is, 1 if the range ends +with $(D withOneOfThese[0]), 2 if it ends with $(D withOneOfThese[1]), and so +on. + */ +uint endsWith(alias pred = "a == b", Range, Needles...)(Range doesThisEnd, Needles withOneOfThese) +if (isBidirectionalRange!Range && Needles.length > 1 && + is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[0])) : bool) && + is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[1 .. $])) : uint)) +{ + alias haystack = doesThisEnd; + alias needles = withOneOfThese; + + // Make one pass looking for empty ranges in needles + foreach (i, Unused; Needles) + { + // Empty range matches everything + static if (!is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) + { + if (needles[i].empty) return i + 1; + } + } + + for (; !haystack.empty; haystack.popBack()) + { + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) + { + // Single-element + if (binaryFun!pred(haystack.back, needles[i])) + { + // found, but continue to account for one-element + // range matches (consider endsWith("ab", "b", + // 'b') should return 1, not 2). + continue; + } + } + else + { + if (binaryFun!pred(haystack.back, needles[i].back)) + continue; + } + + // This code executed on failure to match + // Out with this guy, check for the others + uint result = endsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]); + if (result > i) ++result; + return result; + } + + // If execution reaches this point, then the back matches for all + // needles ranges. What we need to do now is to lop off the back of + // all ranges involved and recurse. + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) + { + // Test has passed in the previous loop + return i + 1; + } + else + { + needles[i].popBack(); + if (needles[i].empty) return i + 1; + } + } + } + return 0; +} + +/// Ditto +bool endsWith(alias pred = "a == b", R1, R2)(R1 doesThisEnd, R2 withThis) +if (isBidirectionalRange!R1 && + isBidirectionalRange!R2 && + is(typeof(binaryFun!pred(doesThisEnd.back, withThis.back)) : bool)) +{ + alias haystack = doesThisEnd; + alias needle = withThis; + + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + + static if (isDefaultPred && isArray!R1 && isArray!R2 && + is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) + { + if (haystack.length < needle.length) return false; + + return haystack[$ - needle.length .. $] == needle; + } + else + { + import std.range : retro; + return startsWith!pred(retro(doesThisEnd), retro(withThis)); + } +} + +/// Ditto +bool endsWith(alias pred = "a == b", R, E)(R doesThisEnd, E withThis) +if (isBidirectionalRange!R && + is(typeof(binaryFun!pred(doesThisEnd.back, withThis)) : bool)) +{ + return doesThisEnd.empty + ? false + : binaryFun!pred(doesThisEnd.back, withThis); +} + +/// +@safe unittest +{ + assert(endsWith("abc", "")); + assert(!endsWith("abc", "b")); + assert(endsWith("abc", "a", 'c') == 2); + assert(endsWith("abc", "c", "a") == 1); + assert(endsWith("abc", "c", "c") == 1); + assert(endsWith("abc", "bc", "c") == 2); + assert(endsWith("abc", "x", "c", "b") == 2); + assert(endsWith("abc", "x", "aa", "bc") == 3); + assert(endsWith("abc", "x", "aaa", "sab") == 0); + assert(endsWith("abc", "x", "aaa", 'c', "sab") == 3); +} + +@safe unittest +{ + import std.algorithm.iteration : filterBidirectional; + import std.typetuple : TypeTuple; + import std.conv : to; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) + { + assert(!endsWith(to!S("abc"), 'a')); + assert(endsWith(to!S("abc"), 'a', 'c') == 2); + assert(!endsWith(to!S("abc"), 'x', 'n', 'b')); + assert(endsWith(to!S("abc"), 'x', 'n', 'c') == 3); + assert(endsWith(to!S("abc\uFF28"), 'a', '\uFF28', 'c') == 2); + + foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + //Lots of strings + assert(endsWith(to!S("abc"), to!T(""))); + assert(!endsWith(to!S("abc"), to!T("a"))); + assert(!endsWith(to!S("abc"), to!T("b"))); + assert(endsWith(to!S("abc"), to!T("bc"), 'c') == 2); + assert(endsWith(to!S("abc"), to!T("a"), "c") == 2); + assert(endsWith(to!S("abc"), to!T("c"), "a") == 1); + assert(endsWith(to!S("abc"), to!T("c"), "c") == 1); + assert(endsWith(to!S("abc"), to!T("x"), 'c', "b") == 2); + assert(endsWith(to!S("abc"), 'x', to!T("aa"), "bc") == 3); + assert(endsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0); + assert(endsWith(to!S("abc"), to!T("x"), "aaa", "c", "sab") == 3); + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co"))); + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2); + + //Unicode + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co"))); + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2); + assert(endsWith(to!S("日本語"), to!T("本語"))); + assert(endsWith(to!S("日本語"), to!T("日本語"))); + assert(!endsWith(to!S("本語"), to!T("日本語"))); + + //Empty + assert(endsWith(to!S(""), T.init)); + assert(!endsWith(to!S(""), 'a')); + assert(endsWith(to!S("a"), T.init)); + assert(endsWith(to!S("a"), T.init, "") == 1); + assert(endsWith(to!S("a"), T.init, 'a') == 1); + assert(endsWith(to!S("a"), 'a', T.init) == 2); + }(); + } + + foreach (T; TypeTuple!(int, short)) + { + immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; + + //RA range + assert(endsWith(arr, cast(int[])null)); + assert(!endsWith(arr, 0)); + assert(!endsWith(arr, 4)); + assert(endsWith(arr, 5)); + assert(endsWith(arr, 0, 4, 5) == 3); + assert(endsWith(arr, [5])); + assert(endsWith(arr, [4, 5])); + assert(endsWith(arr, [4, 5], 7) == 1); + assert(!endsWith(arr, [2, 4, 5])); + assert(endsWith(arr, [2, 4, 5], [3, 4, 5]) == 2); + + //Normal input range + assert(!endsWith(filterBidirectional!"true"(arr), 4)); + assert(endsWith(filterBidirectional!"true"(arr), 5)); + assert(endsWith(filterBidirectional!"true"(arr), [5])); + assert(endsWith(filterBidirectional!"true"(arr), [4, 5])); + assert(endsWith(filterBidirectional!"true"(arr), [4, 5], 7) == 1); + assert(!endsWith(filterBidirectional!"true"(arr), [2, 4, 5])); + assert(endsWith(filterBidirectional!"true"(arr), [2, 4, 5], [3, 4, 5]) == 2); + assert(endsWith(arr, filterBidirectional!"true"([4, 5]))); + assert(endsWith(arr, filterBidirectional!"true"([4, 5]), 7) == 1); + assert(!endsWith(arr, filterBidirectional!"true"([2, 4, 5]))); + assert(endsWith(arr, [2, 4, 5], filterBidirectional!"true"([3, 4, 5])) == 2); + + //Non-default pred + assert(endsWith!("a%10 == b%10")(arr, [14, 15])); + assert(!endsWith!("a%10 == b%10")(arr, [15, 14])); + } +} + +// find +/** +Finds an individual element in an input range. Elements of $(D +haystack) are compared with $(D needle) by using predicate $(D +pred). Performs $(BIGOH walkLength(haystack)) evaluations of $(D +pred). + +To _find the last occurrence of $(D needle) in $(D haystack), call $(D +find(retro(haystack), needle)). See $(XREF range, retro). + +Params: + +pred = The predicate for comparing each element with the needle, defaulting to +$(D "a == b"). +The negated predicate $(D "a != b") can be used to search instead for the first +element $(I not) matching the needle. + +haystack = The $(XREF2 range, isInputRange, input range) searched in. + +needle = The element searched for. + +Constraints: + +$(D isInputRange!InputRange && is(typeof(binaryFun!pred(haystack.front, needle) +: bool))) + +Returns: + +$(D haystack) advanced such that the front element is the one searched for; +that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no +such position exists, returns an empty $(D haystack). + +See_Also: + $(WEB sgi.com/tech/stl/_find.html, STL's _find) + */ +InputRange find(alias pred = "a == b", InputRange, Element)(InputRange haystack, Element needle) +if (isInputRange!InputRange && + is (typeof(binaryFun!pred(haystack.front, needle)) : bool)) +{ + alias R = InputRange; + alias E = Element; + alias predFun = binaryFun!pred; + static if (is(typeof(pred == "a == b"))) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + enum isIntegralNeedle = isSomeChar!E || isIntegral!E || isBoolean!E; + + alias EType = ElementType!R; + + static if (isNarrowString!R) + { + alias EEType = ElementEncodingType!R; + alias UEEType = Unqual!EEType; + + //These are two special cases which can search without decoding the UTF stream. + static if (isDefaultPred && isIntegralNeedle) + { + import std.utf : canSearchInCodeUnits; + + //This special case deals with UTF8 search, when the needle + //is represented by a single code point. + //Note: "needle <= 0x7F" properly handles sign via unsigned promotion + static if (is(UEEType == char)) + { + if (!__ctfe && canSearchInCodeUnits!char(needle)) + { + static R trustedMemchr(ref R haystack, ref E needle) @trusted nothrow pure + { + import core.stdc.string : memchr; + auto ptr = memchr(haystack.ptr, needle, haystack.length); + return ptr ? + haystack[ptr - haystack.ptr .. $] : + haystack[$ .. $]; + } + return trustedMemchr(haystack, needle); + } + } + + //Ditto, but for UTF16 + static if (is(UEEType == wchar)) + { + if (canSearchInCodeUnits!wchar(needle)) + { + foreach (i, ref EEType e; haystack) + { + if (e == needle) + return haystack[i .. $]; + } + return haystack[$ .. $]; + } + } + } + + //Previous conditonal optimizations did not succeed. Fallback to + //unconditional implementations + static if (isDefaultPred) + { + import std.utf : encode; + + //In case of default pred, it is faster to do string/string search. + UEEType[is(UEEType == char) ? 4 : 2] buf; + + size_t len = encode(buf, needle); + return find(haystack, buf[0 .. len]); + } + else + { + import std.utf : decode; + + //Explicit pred: we must test each character by the book. + //We choose a manual decoding approach, because it is faster than + //the built-in foreach, or doing a front/popFront for-loop. + immutable len = haystack.length; + size_t i = 0, next = 0; + while (next < len) + { + if (predFun(decode(haystack, next), needle)) + return haystack[i .. $]; + i = next; + } + return haystack[$ .. $]; + } + } + else static if (isArray!R) + { + //10403 optimization + static if (isDefaultPred && isIntegral!EType && EType.sizeof == 1 && isIntegralNeedle) + { + import std.algorithm.comparison : max, min; + + R findHelper(ref R haystack, ref E needle) @trusted nothrow pure + { + import core.stdc.string : memchr; + + EType* ptr = null; + //Note: we use "min/max" to handle sign mismatch. + if (min(EType.min, needle) == EType.min && + max(EType.max, needle) == EType.max) + { + ptr = cast(EType*) memchr(haystack.ptr, needle, + haystack.length); + } + + return ptr ? + haystack[ptr - haystack.ptr .. $] : + haystack[$ .. $]; + } + + if (!__ctfe) + return findHelper(haystack, needle); + } + + //Default implementation. + foreach (i, ref e; haystack) + if (predFun(e, needle)) + return haystack[i .. $]; + return haystack[$ .. $]; + } + else + { + //Everything else. Walk. + for ( ; !haystack.empty; haystack.popFront() ) + { + if (predFun(haystack.front, needle)) + break; + } + return haystack; + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + assert(find("hello, world", ',') == ", world"); + assert(find([1, 2, 3, 5], 4) == []); + assert(equal(find(SList!int(1, 2, 3, 4, 5)[], 4), SList!int(4, 5)[])); + assert(find!"a > b"([1, 2, 3, 5], 2) == [3, 5]); + + auto a = [ 1, 2, 3 ]; + assert(find(a, 5).empty); // not found + assert(!find(a, 2).empty); // found + + // Case-insensitive find of a string + string[] s = [ "Hello", "world", "!" ]; + assert(!find!("toLower(a) == b")(s, "hello").empty); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto lst = SList!int(1, 2, 5, 7, 3); + assert(lst.front == 1); + auto r = find(lst[], 5); + assert(equal(r, SList!int(5, 7, 3)[])); + assert(find([1, 2, 3, 5], 4).empty); + assert(equal(find!"a > b"("hello", 'k'), "llo")); +} + +@safe pure nothrow unittest +{ + int[] a1 = [1, 2, 3]; + assert(!find ([1, 2, 3], 2).empty); + assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty); + ubyte[] a2 = [1, 2, 3]; + ubyte b2 = 2; + assert(!find ([1, 2, 3], 2).empty); + assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty); +} + +@safe pure unittest +{ + import std.typetuple : TypeTuple; + foreach(R; TypeTuple!(string, wstring, dstring)) + { + foreach(E; TypeTuple!(char, wchar, dchar)) + { + R r1 = "hello world"; + E e1 = 'w'; + assert(find ("hello world", 'w') == "world"); + assert(find!((a,b)=>a==b)("hello world", 'w') == "world"); + R r2 = "日c語"; + E e2 = 'c'; + assert(find ("日c語", 'c') == "c語"); + assert(find!((a,b)=>a==b)("日c語", 'c') == "c語"); + static if (E.sizeof >= 2) + { + R r3 = "hello world"; + E e3 = 'w'; + assert(find ("日本語", '本') == "本語"); + assert(find!((a,b)=>a==b)("日本語", '本') == "本語"); + } + } + } +} + +@safe unittest +{ + //CTFE + static assert (find("abc", 'b') == "bc"); + static assert (find("日b語", 'b') == "b語"); + static assert (find("日本語", '本') == "本語"); + static assert (find([1, 2, 3], 2) == [2, 3]); + + int[] a1 = [1, 2, 3]; + static assert(find ([1, 2, 3], 2)); + static assert(find!((a,b)=>a==b)([1, 2, 3], 2)); + ubyte[] a2 = [1, 2, 3]; + ubyte b2 = 2; + static assert(find ([1, 2, 3], 2)); + static assert(find!((a,b)=>a==b)([1, 2, 3], 2)); +} + +@safe unittest +{ + import std.exception : assertCTFEable; + import std.typetuple : TypeTuple; + + void dg() @safe pure nothrow + { + byte[] sarr = [1, 2, 3, 4]; + ubyte[] uarr = [1, 2, 3, 4]; + foreach(arr; TypeTuple!(sarr, uarr)) + { + foreach(T; TypeTuple!(byte, ubyte, int, uint)) + { + assert(find(arr, cast(T) 3) == arr[2 .. $]); + assert(find(arr, cast(T) 9) == arr[$ .. $]); + } + assert(find(arr, 256) == arr[$ .. $]); + } + } + dg(); + assertCTFEable!dg; +} + +@safe unittest +{ + // Bugzilla 11603 + enum Foo : ubyte { A } + assert([Foo.A].find(Foo.A).empty == false); + + ubyte x = 0; + assert([x].find(x).empty == false); +} + +/** +Advances the input range $(D haystack) by calling $(D haystack.popFront) +until either $(D pred(haystack.front)), or $(D +haystack.empty). Performs $(BIGOH haystack.length) evaluations of $(D +pred). + +To _find the last element of a bidirectional $(D haystack) satisfying +$(D pred), call $(D find!(pred)(retro(haystack))). See $(XREF +range, retro). + +Params: + +pred = The predicate for determining if a given element is the one being +searched for. + +haystack = The $(XREF2 range, isInputRange, input range) to search in. + +Returns: + +$(D haystack) advanced such that the front element is the one searched for; +that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no +such position exists, returns an empty $(D haystack). + +See_Also: + $(WEB sgi.com/tech/stl/find_if.html, STL's find_if) +*/ +InputRange find(alias pred, InputRange)(InputRange haystack) +if (isInputRange!InputRange) +{ + alias R = InputRange; + alias predFun = unaryFun!pred; + static if (isNarrowString!R) + { + import std.utf : decode; + + immutable len = haystack.length; + size_t i = 0, next = 0; + while (next < len) + { + if (predFun(decode(haystack, next))) + return haystack[i .. $]; + i = next; + } + return haystack[$ .. $]; + } + else static if (!isInfinite!R && hasSlicing!R && is(typeof(haystack[cast(size_t)0 .. $]))) + { + size_t i = 0; + for (auto h = haystack.save; !h.empty; h.popFront()) + { + if (predFun(h.front)) + return haystack[i .. $]; + ++i; + } + return haystack[$ .. $]; + } + else + { + //standard range + for ( ; !haystack.empty; haystack.popFront() ) + { + if (predFun(haystack.front)) + break; + } + return haystack; + } +} + +/// +@safe unittest +{ + auto arr = [ 1, 2, 3, 4, 1 ]; + assert(find!("a > 2")(arr) == [ 3, 4, 1 ]); + + // with predicate alias + bool pred(int x) { return x + 1 > 1.5; } + assert(find!(pred)(arr) == arr); +} + +@safe pure unittest +{ + //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] r = [ 1, 2, 3 ]; + assert(find!(a=>a > 2)(r) == [3]); + bool pred(int x) { return x + 1 > 1.5; } + assert(find!(pred)(r) == r); + + assert(find!(a=>a > 'v')("hello world") == "world"); + assert(find!(a=>a%4 == 0)("日本語") == "本語"); +} + +/** +Finds the first occurrence of a forward range in another forward range. + +Performs $(BIGOH walkLength(haystack) * walkLength(needle)) comparisons in the +worst case. There are specializations that improve performance by taking +advantage of bidirectional or random access in the given ranges (where +possible), depending on the statistics of the two ranges' content. + +Params: + +pred = The predicate to use for comparing respective elements from the haystack +and the needle. Defaults to simple equality $(D "a == b"). + +haystack = The $(XREF2 range, isForwardRange, forward range) searched in. + +needle = The $(XREF2 range, isForwardRange, forward range) searched for. + +Returns: + +$(D haystack) advanced such that $(D needle) is a prefix of it (if no +such position exists, returns $(D haystack) advanced to termination). + */ +R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2 + && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool) + && !isRandomAccessRange!R1) +{ + static if (is(typeof(pred == "a == b")) && pred == "a == b" && isSomeString!R1 && isSomeString!R2 + && haystack[0].sizeof == needle[0].sizeof) + { + //return cast(R1) find(representation(haystack), representation(needle)); + // Specialization for simple string search + alias Representation = + Select!(haystack[0].sizeof == 1, ubyte[], + Select!(haystack[0].sizeof == 2, ushort[], uint[])); + // Will use the array specialization + static TO force(TO, T)(T r) @trusted { return cast(TO)r; } + return force!R1(.find!(pred, Representation, Representation) + (force!Representation(haystack), force!Representation(needle))); + } + else + { + return simpleMindedFind!pred(haystack, needle); + } +} + +/// +@safe unittest +{ + import std.container : SList; + + assert(find("hello, world", "World").empty); + assert(find("hello, world", "wo") == "world"); + assert([1, 2, 3, 4].find(SList!int(2, 3)[]) == [2, 3, 4]); + alias C = Tuple!(int, "x", int, "y"); + auto a = [C(1,0), C(2,0), C(3,1), C(4,0)]; + assert(a.find!"a.x == b"([2, 3]) == [C(2,0), C(3,1), C(4,0)]); + assert(a[1 .. $].find!"a.x == b"([2, 3]) == [C(2,0), C(3,1), C(4,0)]); +} + +@safe unittest +{ + import std.container : SList; + alias C = Tuple!(int, "x", int, "y"); + assert([C(1,0), C(2,0), C(3,1), C(4,0)].find!"a.x == b"(SList!int(2, 3)[]) == [C(2,0), C(3,1), C(4,0)]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto lst = SList!int(1, 2, 5, 7, 3); + static assert(isForwardRange!(int[])); + static assert(isForwardRange!(typeof(lst[]))); + auto r = find(lst[], [2, 5]); + assert(equal(r, SList!int(2, 5, 7, 3)[])); +} + +// Specialization for searching a random-access range for a +// bidirectional range +R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isRandomAccessRange!R1 && isBidirectionalRange!R2 + && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) +{ + if (needle.empty) return haystack; + const needleLength = walkLength(needle.save); + if (needleLength > haystack.length) + { + // @@@BUG@@@ + //return haystack[$ .. $]; + return haystack[haystack.length .. haystack.length]; + } + // @@@BUG@@@ + // auto needleBack = moveBack(needle); + // Stage 1: find the step + size_t step = 1; + auto needleBack = needle.back; + needle.popBack(); + for (auto i = needle.save; !i.empty && i.back != needleBack; + i.popBack(), ++step) + { + } + // Stage 2: linear find + size_t scout = needleLength - 1; + for (;;) + { + if (scout >= haystack.length) + { + return haystack[haystack.length .. haystack.length]; + } + if (!binaryFun!pred(haystack[scout], needleBack)) + { + ++scout; + continue; + } + // Found a match with the last element in the needle + auto cand = haystack[scout + 1 - needleLength .. haystack.length]; + if (startsWith!pred(cand, needle)) + { + // found + return cand; + } + // Continue with the stride + scout += step; + } +} + +@safe unittest +{ + //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); + // @@@BUG@@@ removing static below makes unittest fail + static struct BiRange + { + int[] payload; + @property bool empty() { return payload.empty; } + @property BiRange save() { return this; } + @property ref int front() { return payload[0]; } + @property ref int back() { return payload[$ - 1]; } + void popFront() { return payload.popFront(); } + void popBack() { return payload.popBack(); } + } + //static assert(isBidirectionalRange!BiRange); + auto r = BiRange([1, 2, 3, 10, 11, 4]); + //assert(equal(find(r, [3, 10]), BiRange([3, 10, 11, 4]))); + //assert(find("abc", "bc").length == 2); + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + //assert(find!"a == b"("abc", "bc").length == 2); +} + +// Leftover specialization: searching a random-access range for a +// non-bidirectional forward range +R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2 && + is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) +{ + static if (!is(ElementType!R1 == ElementType!R2)) + { + return simpleMindedFind!pred(haystack, needle); + } + else + { + // Prepare the search with needle's first element + if (needle.empty) + return haystack; + + haystack = .find!pred(haystack, needle.front); + + static if (hasLength!R1 && hasLength!R2 && is(typeof(takeNone(haystack)) == R1)) + { + if (needle.length > haystack.length) + return takeNone(haystack); + } + else + { + if (haystack.empty) + return haystack; + } + + needle.popFront(); + size_t matchLen = 1; + + // Loop invariant: haystack[0 .. matchLen] matches everything in + // the initial needle that was popped out of needle. + for (;;) + { + // Extend matchLength as much as possible + for (;;) + { + import std.range : takeNone; + + if (needle.empty || haystack.empty) + return haystack; + + static if (hasLength!R1 && is(typeof(takeNone(haystack)) == R1)) + { + if (matchLen == haystack.length) + return takeNone(haystack); + } + + if (!binaryFun!pred(haystack[matchLen], needle.front)) + break; + + ++matchLen; + needle.popFront(); + } + + auto bestMatch = haystack[0 .. matchLen]; + haystack.popFront(); + haystack = .find!pred(haystack, bestMatch); + } + } +} + +@safe unittest +{ + import std.container : SList; + + assert(find([ 1, 2, 3 ], SList!int(2, 3)[]) == [ 2, 3 ]); + assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]); +} + +//Bug# 8334 +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.range; + + auto haystack = [1, 2, 3, 4, 1, 9, 12, 42]; + auto needle = [12, 42, 27]; + + //different overload of find, but it's the base case. + assert(find(haystack, needle).empty); + + assert(find(haystack, takeExactly(filter!"true"(needle), 3)).empty); + assert(find(haystack, filter!"true"(needle)).empty); +} + +// Internally used by some find() overloads above. Can't make it +// private due to bugs in the compiler. +/*private*/ R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, R2 needle) +{ + enum estimateNeedleLength = hasLength!R1 && !hasLength!R2; + + static if (hasLength!R1) + { + static if (!hasLength!R2) + size_t estimatedNeedleLength = 0; + else + immutable size_t estimatedNeedleLength = needle.length; + } + + bool haystackTooShort() + { + static if (estimateNeedleLength) + { + return haystack.length < estimatedNeedleLength; + } + else + { + return haystack.empty; + } + } + + searching: + for (;; haystack.popFront()) + { + if (haystackTooShort()) + { + // Failed search + static if (hasLength!R1) + { + static if (is(typeof(haystack[haystack.length .. + haystack.length]) : R1)) + return haystack[haystack.length .. haystack.length]; + else + return R1.init; + } + else + { + assert(haystack.empty); + return haystack; + } + } + static if (estimateNeedleLength) + size_t matchLength = 0; + for (auto h = haystack.save, n = needle.save; + !n.empty; + h.popFront(), n.popFront()) + { + if (h.empty || !binaryFun!pred(h.front, n.front)) + { + // Failed searching n in h + static if (estimateNeedleLength) + { + if (estimatedNeedleLength < matchLength) + estimatedNeedleLength = matchLength; + } + continue searching; + } + static if (estimateNeedleLength) + ++matchLength; + } + break; + } + return haystack; +} + +@safe unittest +{ + // Test simpleMindedFind for the case where both haystack and needle have + // length. + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + struct CustomString + { + @safe: + string _impl; + + // This is what triggers issue 7992. + @property size_t length() const { return _impl.length; } + @property void length(size_t len) { _impl.length = len; } + + // This is for conformance to the forward range API (we deliberately + // make it non-random access so that we will end up in + // simpleMindedFind). + @property bool empty() const { return _impl.empty; } + @property dchar front() const { return _impl.front; } + void popFront() { _impl.popFront(); } + @property CustomString save() { return this; } + } + + // If issue 7992 occurs, this will throw an exception from calling + // popFront() on an empty range. + auto r = find(CustomString("a"), CustomString("b")); +} + +/** +Finds two or more $(D needles) into a $(D haystack). The predicate $(D +pred) is used throughout to compare elements. By default, elements are +compared for equality. + +Params: + +pred = The predicate to use for comparing elements. + +haystack = The target of the search. Must be an input range. +If any of $(D needles) is a range with elements comparable to +elements in $(D haystack), then $(D haystack) must be a forward range +such that the search can backtrack. + +needles = One or more items to search for. Each of $(D needles) must +be either comparable to one element in $(D haystack), or be itself a +forward range with elements comparable with elements in +$(D haystack). + +Returns: + +A tuple containing $(D haystack) positioned to match one of the +needles and also the 1-based index of the matching element in $(D +needles) (0 if none of $(D needles) matched, 1 if $(D needles[0]) +matched, 2 if $(D needles[1]) matched...). The first needle to be found +will be the one that matches. If multiple needles are found at the +same spot in the range, then the shortest one is the one which matches +(if multiple needles of the same length are found at the same spot (e.g +$(D "a") and $(D 'a')), then the left-most of them in the argument list +matches). + +The relationship between $(D haystack) and $(D needles) simply means +that one can e.g. search for individual $(D int)s or arrays of $(D +int)s in an array of $(D int)s. In addition, if elements are +individually comparable, searches of heterogeneous types are allowed +as well: a $(D double[]) can be searched for an $(D int) or a $(D +short[]), and conversely a $(D long) can be searched for a $(D float) +or a $(D double[]). This makes for efficient searches without the need +to coerce one side of the comparison into the other's side type. + +The complexity of the search is $(BIGOH haystack.length * +max(needles.length)). (For needles that are individual items, length +is considered to be 1.) The strategy used in searching several +subranges at once maximizes cache usage by moving in $(D haystack) as +few times as possible. + */ +Tuple!(Range, size_t) find(alias pred = "a == b", Range, Ranges...) +(Range haystack, Ranges needles) +if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles)))) +{ + for (;; haystack.popFront()) + { + size_t r = startsWith!pred(haystack, needles); + if (r || haystack.empty) + { + return tuple(haystack, r); + } + } +} + +/// +@safe unittest +{ + int[] a = [ 1, 4, 2, 3 ]; + assert(find(a, 4) == [ 4, 2, 3 ]); + assert(find(a, [ 1, 4 ]) == [ 1, 4, 2, 3 ]); + assert(find(a, [ 1, 3 ], 4) == tuple([ 4, 2, 3 ], 2)); + // Mixed types allowed if comparable + assert(find(a, 5, [ 1.2, 3.5 ], 2.0) == tuple([ 2, 3 ], 3)); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto s1 = "Mary has a little lamb"; + //writeln(find(s1, "has a", "has an")); + assert(find(s1, "has a", "has an") == tuple("has a little lamb", 1)); + assert(find(s1, 't', "has a", "has an") == tuple("has a little lamb", 2)); + assert(find(s1, 't', "has a", 'y', "has an") == tuple("y has a little lamb", 3)); + assert(find("abc", "bc").length == 2); +} + +@safe unittest +{ + import std.algorithm : rndstuff; // FIXME + import std.typetuple : TypeTuple; + import std.uni : toUpper; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = [ 1, 2, 3 ]; + assert(find(a, 5).empty); + assert(find(a, 2) == [2, 3]); + + foreach (T; TypeTuple!(int, double)) + { + auto b = rndstuff!(T)(); + if (!b.length) continue; + b[$ / 2] = 200; + b[$ / 4] = 200; + assert(find(b, 200).length == b.length - b.length / 4); + } + + // Case-insensitive find of a string + string[] s = [ "Hello", "world", "!" ]; + //writeln(find!("toUpper(a) == toUpper(b)")(s, "hello")); + assert(find!("toUpper(a) == toUpper(b)")(s, "hello").length == 3); + + static bool f(string a, string b) { return toUpper(a) == toUpper(b); } + assert(find!(f)(s, "hello").length == 3); +} + +@safe unittest +{ + import std.algorithm : rndstuff; // FIXME + import std.algorithm.comparison : equal; + import std.typetuple : TypeTuple; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = [ 1, 2, 3, 2, 6 ]; + assert(find(std.range.retro(a), 5).empty); + assert(equal(find(std.range.retro(a), 2), [ 2, 3, 2, 1 ][])); + + foreach (T; TypeTuple!(int, double)) + { + auto b = rndstuff!(T)(); + if (!b.length) continue; + b[$ / 2] = 200; + b[$ / 4] = 200; + assert(find(std.range.retro(b), 200).length == + b.length - (b.length - 1) / 2); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(find(a, b) == [ 1, 2, 3, 4, 5 ]); + assert(find(b, a).empty); + + foreach (DummyType; AllDummyRanges) { + DummyType d; + auto findRes = find(d, 5); + assert(equal(findRes, [5,6,7,8,9,10])); + } +} + +/** + * Finds $(D needle) in $(D haystack) efficiently using the + * $(LUCKY Boyer-Moore) method. + * + * Params: + * haystack = A random-access range with length and slicing. + * needle = A $(LREF BoyerMooreFinder). + * + * Returns: + * $(D haystack) advanced such that $(D needle) is a prefix of it (if no + * such position exists, returns $(D haystack) advanced to termination). + */ +Range1 find(Range1, alias pred, Range2)( + Range1 haystack, BoyerMooreFinder!(pred, Range2) needle) +{ + return needle.beFound(haystack); +} + +@safe unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + string h = "/homes/aalexand/d/dmd/bin/../lib/libphobos.a(dmain2.o)"~ + "(.gnu.linkonce.tmain+0x74): In function `main' undefined reference"~ + " to `_Dmain':"; + string[] ns = ["libphobos", "function", " undefined", "`", ":"]; + foreach (n ; ns) { + auto p = find(h, boyerMooreFinder(n)); + assert(!p.empty); + } +} + +/// +@safe unittest +{ + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + + assert(find(a, boyerMooreFinder(b)) == [ 1, 2, 3, 4, 5 ]); + assert(find(b, boyerMooreFinder(a)).empty); +} + +@safe unittest +{ + auto bm = boyerMooreFinder("for"); + auto match = find("Moor", bm); + assert(match.empty); +} + +// canFind +/++ +Convenience function. Like find, but only returns whether or not the search +was successful. + +See_Also: +$(LREF among) for checking a value against multiple possibilities. + +/ +template canFind(alias pred="a == b") +{ + import std.typetuple : allSatisfy; + + /++ + Returns $(D true) if and only if any value $(D v) found in the + input range $(D range) satisfies the predicate $(D pred). + Performs (at most) $(BIGOH haystack.length) evaluations of $(D pred). + +/ + bool canFind(Range)(Range haystack) + if (is(typeof(find!pred(haystack)))) + { + return any!pred(haystack); + } + + /++ + Returns $(D true) if and only if $(D needle) can be found in $(D + range). Performs $(BIGOH haystack.length) evaluations of $(D pred). + +/ + bool canFind(Range, Element)(Range haystack, Element needle) + if (is(typeof(find!pred(haystack, needle)))) + { + return !find!pred(haystack, needle).empty; + } + + /++ + Returns the 1-based index of the first needle found in $(D haystack). If no + needle is found, then $(D 0) is returned. + + So, if used directly in the condition of an if statement or loop, the result + will be $(D true) if one of the needles is found and $(D false) if none are + found, whereas if the result is used elsewhere, it can either be cast to + $(D bool) for the same effect or used to get which needle was found first + without having to deal with the tuple that $(D LREF find) returns for the + same operation. + +/ + size_t canFind(Range, Ranges...)(Range haystack, Ranges needles) + if (Ranges.length > 1 && + allSatisfy!(isForwardRange, Ranges) && + is(typeof(find!pred(haystack, needles)))) + { + return find!pred(haystack, needles)[1]; + } +} + +/// +@safe unittest +{ + assert(canFind([0, 1, 2, 3], 2) == true); + assert(canFind([0, 1, 2, 3], [1, 2], [2, 3])); + assert(canFind([0, 1, 2, 3], [1, 2], [2, 3]) == 1); + assert(canFind([0, 1, 2, 3], [1, 7], [2, 3])); + assert(canFind([0, 1, 2, 3], [1, 7], [2, 3]) == 2); + + assert(canFind([0, 1, 2, 3], 4) == false); + assert(!canFind([0, 1, 2, 3], [1, 3], [2, 4])); + assert(canFind([0, 1, 2, 3], [1, 3], [2, 4]) == 0); +} + +@safe unittest +{ + import std.algorithm : rndstuff; // FIXME + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto a = rndstuff!(int)(); + if (a.length) + { + auto b = a[a.length / 2]; + assert(canFind(a, b)); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + assert(equal!(canFind!"a < b")([[1, 2, 3], [7, 8, 9]], [2, 8])); +} + +// findAdjacent +/** +Advances $(D r) until it finds the first two adjacent elements $(D a), +$(D b) that satisfy $(D pred(a, b)). Performs $(BIGOH r.length) +evaluations of $(D pred). + +Params: + pred = The predicate to satisfy. + r = A $(XREF2 range, isForwardRange, forward range) to search in. + +Returns: +$(D r) advanced to the first occurrence of two adjacent elements that satisfy +the given predicate. If there are no such two elements, returns $(D r) advanced +until empty. + +See_Also: + $(WEB sgi.com/tech/stl/adjacent_find.html, STL's adjacent_find) +*/ +Range findAdjacent(alias pred = "a == b", Range)(Range r) + if (isForwardRange!(Range)) +{ + auto ahead = r.save; + if (!ahead.empty) + { + for (ahead.popFront(); !ahead.empty; r.popFront(), ahead.popFront()) + { + if (binaryFun!(pred)(r.front, ahead.front)) return r; + } + } + static if (!isInfinite!Range) + return ahead; +} + +/// +@safe unittest +{ + int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; + auto r = findAdjacent(a); + assert(r == [ 10, 10, 9, 8, 8, 7, 8, 9 ]); + auto p = findAdjacent!("a < b")(a); + assert(p == [ 7, 8, 9 ]); + +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.range; + + //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; + auto p = findAdjacent(a); + assert(p == [10, 10, 9, 8, 8, 7, 8, 9 ]); + p = findAdjacent!("a < b")(a); + assert(p == [7, 8, 9]); + // empty + a = []; + p = findAdjacent(a); + assert(p.empty); + // not found + a = [ 1, 2, 3, 4, 5 ]; + p = findAdjacent(a); + assert(p.empty); + p = findAdjacent!"a > b"(a); + assert(p.empty); + ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]); + assert(equal(findAdjacent(rfr), [2, 2, 3])); + + // Issue 9350 + assert(!repeat(1).findAdjacent().empty); +} + +// findAmong +/** +Searches the given range for an element that matches one of the given choices. + +Advances $(D seq) by calling $(D seq.popFront) until either +$(D find!(pred)(choices, seq.front)) is $(D true), or $(D seq) becomes empty. +Performs $(BIGOH seq.length * choices.length) evaluations of $(D pred). + +Params: + pred = The predicate to use for determining a match. + seq = The $(XREF2 range, isInputRange, input range) to search. + choices = A $(XREF2 range, isForwardRange, forward range) of possible + choices. + +Returns: +$(D seq) advanced to the first matching element, or until empty if there are no +matching elements. + +See_Also: + $(WEB sgi.com/tech/stl/find_first_of.html, STL's find_first_of) +*/ +Range1 findAmong(alias pred = "a == b", Range1, Range2)( + Range1 seq, Range2 choices) + if (isInputRange!Range1 && isForwardRange!Range2) +{ + for (; !seq.empty && find!pred(choices, seq.front).empty; seq.popFront()) + { + } + return seq; +} + +/// +@safe unittest +{ + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 3, 1, 2 ]; + assert(findAmong(a, b) == a[2 .. $]); +} + +@safe unittest +{ + //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ -1, 0, 2, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(findAmong(a, b) == [2, 1, 2, 3, 4, 5 ]); + assert(findAmong(b, [ 4, 6, 7 ][]).empty); + assert(findAmong!("a == b")(a, b).length == a.length - 2); + assert(findAmong!("a == b")(b, [ 4, 6, 7 ][]).empty); +} + +// findSkip +/** + * Finds $(D needle) in $(D haystack) and positions $(D haystack) + * right after the first occurrence of $(D needle). + * + * Params: + * haystack = The $(XREF2 range, isForwardRange, forward range) to search in. + * needle = The $(XREF2 range, isForwardRange, forward range) to search for. + * + * Returns: $(D true) if the needle was found, in which case $(D haystack) is + * positioned after the end of the first occurrence of $(D needle); otherwise + * $(D false), leaving $(D haystack) untouched. + */ +bool findSkip(alias pred = "a == b", R1, R2)(ref R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2 + && is(typeof(binaryFun!pred(haystack.front, needle.front)))) +{ + auto parts = findSplit!pred(haystack, needle); + if (parts[1].empty) return false; + // found + haystack = parts[2]; + return true; +} + +/// +@safe unittest +{ + // Needle is found; s is replaced by the substring following the first + // occurrence of the needle. + string s = "abcdef"; + assert(findSkip(s, "cd") && s == "ef"); + + // Needle is not found; s is left untouched. + s = "abcdef"; + assert(!findSkip(s, "cxd") && s == "abcdef"); + + // If the needle occurs at the end of the range, the range is left empty. + s = "abcdef"; + assert(findSkip(s, "def") && s.empty); +} + +/** +These functions find the first occurrence of $(D needle) in $(D +haystack) and then split $(D haystack) as follows. + +$(D findSplit) returns a tuple $(D result) containing $(I three) +ranges. $(D result[0]) is the portion of $(D haystack) before $(D +needle), $(D result[1]) is the portion of $(D haystack) that matches +$(D needle), and $(D result[2]) is the portion of $(D haystack) after +the match. If $(D needle) was not found, $(D result[0]) +comprehends $(D haystack) entirely and $(D result[1]) and $(D result[2]) +are empty. + +$(D findSplitBefore) returns a tuple $(D result) containing two +ranges. $(D result[0]) is the portion of $(D haystack) before $(D +needle), and $(D result[1]) is the balance of $(D haystack) starting +with the match. If $(D needle) was not found, $(D result[0]) +comprehends $(D haystack) entirely and $(D result[1]) is empty. + +$(D findSplitAfter) returns a tuple $(D result) containing two ranges. +$(D result[0]) is the portion of $(D haystack) up to and including the +match, and $(D result[1]) is the balance of $(D haystack) starting +after the match. If $(D needle) was not found, $(D result[0]) is empty +and $(D result[1]) is $(D haystack). + +In all cases, the concatenation of the returned ranges spans the +entire $(D haystack). + +If $(D haystack) is a random-access range, all three components of the +tuple have the same type as $(D haystack). Otherwise, $(D haystack) +must be a forward range and the type of $(D result[0]) and $(D +result[1]) is the same as $(XREF range,takeExactly). + */ +auto findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2) +{ + static if (isSomeString!R1 && isSomeString!R2 + || isRandomAccessRange!R1 && hasLength!R2) + { + auto balance = find!pred(haystack, needle); + immutable pos1 = haystack.length - balance.length; + immutable pos2 = balance.empty ? pos1 : pos1 + needle.length; + return tuple(haystack[0 .. pos1], + haystack[pos1 .. pos2], + haystack[pos2 .. haystack.length]); + } + else + { + import std.range : takeExactly; + auto original = haystack.save; + auto h = haystack.save; + auto n = needle.save; + size_t pos1, pos2; + while (!n.empty && !h.empty) + { + if (binaryFun!pred(h.front, n.front)) + { + h.popFront(); + n.popFront(); + ++pos2; + } + else + { + haystack.popFront(); + n = needle.save; + h = haystack.save; + pos2 = ++pos1; + } + } + return tuple(takeExactly(original, pos1), + takeExactly(haystack, pos2 - pos1), + h); + } +} + +/// Ditto +auto findSplitBefore(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2) +{ + static if (isSomeString!R1 && isSomeString!R2 + || isRandomAccessRange!R1 && hasLength!R2) + { + auto balance = find!pred(haystack, needle); + immutable pos = haystack.length - balance.length; + return tuple(haystack[0 .. pos], haystack[pos .. haystack.length]); + } + else + { + import std.range : takeExactly; + auto original = haystack.save; + auto h = haystack.save; + auto n = needle.save; + size_t pos; + while (!n.empty && !h.empty) + { + if (binaryFun!pred(h.front, n.front)) + { + h.popFront(); + n.popFront(); + } + else + { + haystack.popFront(); + n = needle.save; + h = haystack.save; + ++pos; + } + } + return tuple(takeExactly(original, pos), haystack); + } +} + +/// Ditto +auto findSplitAfter(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2) +{ + static if (isSomeString!R1 && isSomeString!R2 + || isRandomAccessRange!R1 && hasLength!R2) + { + auto balance = find!pred(haystack, needle); + immutable pos = balance.empty ? 0 : haystack.length - balance.length + needle.length; + return tuple(haystack[0 .. pos], haystack[pos .. haystack.length]); + } + else + { + import std.range : takeExactly; + auto original = haystack.save; + auto h = haystack.save; + auto n = needle.save; + size_t pos1, pos2; + while (!n.empty) + { + if (h.empty) + { + // Failed search + return tuple(takeExactly(original, 0), original); + } + if (binaryFun!pred(h.front, n.front)) + { + h.popFront(); + n.popFront(); + ++pos2; + } + else + { + haystack.popFront(); + n = needle.save; + h = haystack.save; + pos2 = ++pos1; + } + } + return tuple(takeExactly(original, pos2), h); + } +} + +/// +@safe unittest +{ + auto a = "Carl Sagan Memorial Station"; + auto r = findSplit(a, "Velikovsky"); + assert(r[0] == a); + assert(r[1].empty); + assert(r[2].empty); + r = findSplit(a, " "); + assert(r[0] == "Carl"); + assert(r[1] == " "); + assert(r[2] == "Sagan Memorial Station"); + auto r1 = findSplitBefore(a, "Sagan"); + assert(r1[0] == "Carl ", r1[0]); + assert(r1[1] == "Sagan Memorial Station"); + auto r2 = findSplitAfter(a, "Sagan"); + assert(r2[0] == "Carl Sagan"); + assert(r2[1] == " Memorial Station"); +} + +@safe unittest +{ + auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + auto r = findSplit(a, [9, 1]); + assert(r[0] == a); + assert(r[1].empty); + assert(r[2].empty); + r = findSplit(a, [3]); + assert(r[0] == a[0 .. 2]); + assert(r[1] == a[2 .. 3]); + assert(r[2] == a[3 .. $]); + + auto r1 = findSplitBefore(a, [9, 1]); + assert(r1[0] == a); + assert(r1[1].empty); + r1 = findSplitBefore(a, [3, 4]); + assert(r1[0] == a[0 .. 2]); + assert(r1[1] == a[2 .. $]); + + r1 = findSplitAfter(a, [9, 1]); + assert(r1[0].empty); + assert(r1[1] == a); + r1 = findSplitAfter(a, [3, 4]); + assert(r1[0] == a[0 .. 4]); + assert(r1[1] == a[4 .. $]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + auto fwd = filter!"a > 0"(a); + auto r = findSplit(fwd, [9, 1]); + assert(equal(r[0], a)); + assert(r[1].empty); + assert(r[2].empty); + r = findSplit(fwd, [3]); + assert(equal(r[0], a[0 .. 2])); + assert(equal(r[1], a[2 .. 3])); + assert(equal(r[2], a[3 .. $])); + + auto r1 = findSplitBefore(fwd, [9, 1]); + assert(equal(r1[0], a)); + assert(r1[1].empty); + r1 = findSplitBefore(fwd, [3, 4]); + assert(equal(r1[0], a[0 .. 2])); + assert(equal(r1[1], a[2 .. $])); + + r1 = findSplitAfter(fwd, [9, 1]); + assert(r1[0].empty); + assert(equal(r1[1], a)); + r1 = findSplitAfter(fwd, [3, 4]); + assert(equal(r1[0], a[0 .. 4])); + assert(equal(r1[1], a[4 .. $])); +} + +/** +Returns the minimum element of a range together with the number of +occurrences. The function can actually be used for counting the +maximum or any other ordering predicate (that's why $(D maxCount) is +not provided). + */ +Tuple!(ElementType!Range, size_t) +minCount(alias pred = "a < b", Range)(Range range) + if (isInputRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + import std.exception : enforce; + + alias T = ElementType!Range; + alias UT = Unqual!T; + alias RetType = Tuple!(T, size_t); + + static assert (is(typeof(RetType(range.front, 1))), + algoFormat("Error: Cannot call minCount on a %s, because it is not possible "~ + "to copy the result value (a %s) into a Tuple.", Range.stringof, T.stringof)); + + enforce(!range.empty, "Can't count elements from an empty range"); + size_t occurrences = 1; + + static if (isForwardRange!Range) + { + Range least = range.save; + for (range.popFront(); !range.empty; range.popFront()) + { + if (binaryFun!pred(least.front, range.front)) continue; + if (binaryFun!pred(range.front, least.front)) + { + // change the min + least = range.save; + occurrences = 1; + } + else + ++occurrences; + } + return RetType(least.front, occurrences); + } + else static if (isAssignable!(UT, T) || (!hasElaborateAssign!UT && isAssignable!UT)) + { + UT v = UT.init; + static if (isAssignable!(UT, T)) v = range.front; + else v = cast(UT)range.front; + + for (range.popFront(); !range.empty; range.popFront()) + { + if (binaryFun!pred(*cast(T*)&v, range.front)) continue; + if (binaryFun!pred(range.front, *cast(T*)&v)) + { + // change the min + static if (isAssignable!(UT, T)) v = range.front; + else v = cast(UT)range.front; //Safe because !hasElaborateAssign!UT + occurrences = 1; + } + else + ++occurrences; + } + return RetType(*cast(T*)&v, occurrences); + } + else static if (hasLvalueElements!Range) + { + import std.algorithm : addressOf; // FIXME + T* p = addressOf(range.front); + for (range.popFront(); !range.empty; range.popFront()) + { + if (binaryFun!pred(*p, range.front)) continue; + if (binaryFun!pred(range.front, *p)) + { + // change the min + p = addressOf(range.front); + occurrences = 1; + } + else + ++occurrences; + } + return RetType(*p, occurrences); + } + else + static assert(false, + algoFormat("Sorry, can't find the minCount of a %s: Don't know how "~ + "to keep track of the smallest %s element.", Range.stringof, T.stringof)); +} + +/// +unittest +{ + import std.conv : text; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + // Minimum is 1 and occurs 3 times + assert(minCount(a) == tuple(1, 3)); + // Maximum is 4 and occurs 2 times + assert(minCount!("a > b")(a) == tuple(4, 2)); +} + +unittest +{ + import std.conv : text; + import std.exception : assertThrown; + import std.internal.test.dummyrange; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[][] b = [ [4], [2, 4], [4], [4] ]; + auto c = minCount!("a[0] < b[0]")(b); + assert(c == tuple([2, 4], 1), text(c[0])); + + //Test empty range + assertThrown(minCount(b[$..$])); + + //test with reference ranges. Test both input and forward. + assert(minCount(new ReferenceInputRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2)); + assert(minCount(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2)); +} + +unittest +{ + import std.conv : text; + import std.typetuple : TypeTuple; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + static struct R(T) //input range + { + T[] arr; + alias arr this; + } + + immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + R!(immutable int) b = R!(immutable int)(a); + + assert(minCount(a) == tuple(1, 3)); + assert(minCount(b) == tuple(1, 3)); + assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(a) == tuple(4, 2)); + assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(b) == tuple(4, 2)); + + immutable(int[])[] c = [ [4], [2, 4], [4], [4] ]; + assert(minCount!("a[0] < b[0]")(c) == tuple([2, 4], 1), text(c[0])); + + static struct S1 + { + int i; + } + alias IS1 = immutable(S1); + static assert( isAssignable!S1); + static assert( isAssignable!(S1, IS1)); + + static struct S2 + { + int* p; + this(ref immutable int i) immutable {p = &i;} + this(ref int i) {p = &i;} + @property ref inout(int) i() inout {return *p;} + bool opEquals(const S2 other) const {return i == other.i;} + } + alias IS2 = immutable(S2); + static assert( isAssignable!S2); + static assert(!isAssignable!(S2, IS2)); + static assert(!hasElaborateAssign!S2); + + static struct S3 + { + int i; + void opAssign(ref S3 other) @disable; + } + static assert(!isAssignable!S3); + + foreach (Type; TypeTuple!(S1, IS1, S2, IS2, S3)) + { + static if (is(Type == immutable)) alias V = immutable int; + else alias V = int; + V one = 1, two = 2; + auto r1 = [Type(two), Type(one), Type(one)]; + auto r2 = R!Type(r1); + assert(minCount!"a.i < b.i"(r1) == tuple(Type(one), 2)); + assert(minCount!"a.i < b.i"(r2) == tuple(Type(one), 2)); + assert(one == 1 && two == 2); + } +} + +// minPos +/** +Returns the position of the minimum element of forward range $(D +range), i.e. a subrange of $(D range) starting at the position of its +smallest element and with the same ending as $(D range). The function +can actually be used for finding the maximum or any other ordering +predicate (that's why $(D maxPos) is not provided). + */ +Range minPos(alias pred = "a < b", Range)(Range range) + if (isForwardRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + if (range.empty) return range; + auto result = range.save; + + for (range.popFront(); !range.empty; range.popFront()) + { + //Note: Unlike minCount, we do not care to find equivalence, so a single pred call is enough + if (binaryFun!pred(range.front, result.front)) + { + // change the min + result = range.save; + } + } + return result; +} + +/// +@safe unittest +{ + int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + // Minimum is 1 and first occurs in position 3 + assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]); + // Maximum is 4 and first occurs in position 2 + assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + //Test that an empty range works + int[] b = a[$..$]; + assert(equal(minPos(b), b)); + + //test with reference range. + assert( equal( minPos(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])), [0, 2, 0] ) ); +} + +unittest +{ + //Rvalue range + import std.algorithm.comparison : equal; + import std.container : Array; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + assert(Array!int(2, 3, 4, 1, 2, 4, 1, 1, 2) + [] + .minPos() + .equal([ 1, 2, 4, 1, 1, 2 ])); +} + +@safe unittest +{ + //BUG 9299 + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + // Minimum is 1 and first occurs in position 3 + assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]); + // Maximum is 4 and first occurs in position 5 + assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]); + + immutable(int[])[] b = [ [4], [2, 4], [4], [4] ]; + assert(minPos!("a[0] < b[0]")(b) == [ [2, 4], [4], [4] ]); +} + +/** +Skip over the initial portion of the first given range that matches the second +range, or do nothing if there is no match. + +Params: + pred = The predicate that determines whether elements from each respective + range match. Defaults to equality $(D "a == b"). + r1 = The $(XREF2 range, isForwardRange, forward range) to move forward. + r2 = The $(XREF2 range, isInputRange, input range) representing the initial + segment of $(D r1) to skip over. + +Returns: +true if the initial segment of $(D r1) matches $(D r2), and $(D r1) has been +advanced to the point past this segment; otherwise false, and $(D r1) is left +in its original position. + */ +bool skipOver(alias pred = "a == b", R1, R2)(ref R1 r1, R2 r2) + if (is(typeof(binaryFun!pred(r1.front, r2.front))) && + isForwardRange!R1 && + isInputRange!R2) +{ + auto r = r1.save; + while (!r2.empty && !r.empty && binaryFun!pred(r.front, r2.front)) + { + r.popFront(); + r2.popFront(); + } + if (r2.empty) + r1 = r; + return r2.empty; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto s1 = "Hello world"; + assert(!skipOver(s1, "Ha")); + assert(s1 == "Hello world"); + assert(skipOver(s1, "Hell") && s1 == "o world"); + + string[] r1 = ["abc", "def", "hij"]; + dstring[] r2 = ["abc"d]; + assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d])); + assert(r1 == ["abc", "def", "hij"]); + assert(skipOver!((a, b) => a.equal(b))(r1, r2)); + assert(r1 == ["def", "hij"]); +} + +/** +Skip over the first element of the given range if it matches the given element, +otherwise do nothing. + +Params: + pred = The predicate that determines whether an element from the range + matches the given element. + + r = The $(XREF range, isInputRange, input range) to skip over. + + e = The element to match. + +Returns: +true if the first element matches the given element according to the given +predicate, and the range has been advanced by one element; otherwise false, and +the range is left untouched. + */ +bool skipOver(alias pred = "a == b", R, E)(ref R r, E e) + if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R) +{ + if (r.empty || !binaryFun!pred(r.front, e)) + return false; + r.popFront(); + return true; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto s1 = "Hello world"; + assert(!skipOver(s1, 'a')); + assert(s1 == "Hello world"); + assert(skipOver(s1, 'H') && s1 == "ello world"); + + string[] r = ["abc", "def", "hij"]; + dstring e = "abc"d; + assert(!skipOver!((a, b) => a.equal(b))(r, "def"d)); + assert(r == ["abc", "def", "hij"]); + assert(skipOver!((a, b) => a.equal(b))(r, e)); + assert(r == ["def", "hij"]); + + auto s2 = ""; + assert(!s2.skipOver('a')); +} + +/** +Checks whether the given $(XREF2 range, isInputRange, input range) starts with +(one of) the given needle(s). + +Params: + + pred = Predicate to use in comparing the elements of the haystack and the + needle(s). + + doesThisStart = The input range to check. + + withOneOfThese = The needles against which the range is to be checked, + which may be individual elements or input ranges of elements. + + withThis = The single needle to check, which may be either a single element + or an input range of elements. + +Returns: + +0 if the needle(s) do not occur at the beginning of the given range; +otherwise the position of the matching needle, that is, 1 if the range starts +with $(D withOneOfThese[0]), 2 if it starts with $(D withOneOfThese[1]), and so +on. + +In the case where $(D doesThisStart) starts with multiple of the ranges or +elements in $(D withOneOfThese), then the shortest one matches (if there are +two which match which are of the same length (e.g. $(D "a") and $(D 'a')), then +the left-most of them in the argument +list matches). + */ +uint startsWith(alias pred = "a == b", Range, Needles...)(Range doesThisStart, Needles withOneOfThese) +if (isInputRange!Range && Needles.length > 1 && + is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) : bool ) && + is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $])) : uint)) +{ + alias haystack = doesThisStart; + alias needles = withOneOfThese; + + // Make one pass looking for empty ranges in needles + foreach (i, Unused; Needles) + { + // Empty range matches everything + static if (!is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) + { + if (needles[i].empty) return i + 1; + } + } + + for (; !haystack.empty; haystack.popFront()) + { + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) + { + // Single-element + if (binaryFun!pred(haystack.front, needles[i])) + { + // found, but instead of returning, we just stop searching. + // This is to account for one-element + // range matches (consider startsWith("ab", "a", + // 'a') should return 1, not 2). + break; + } + } + else + { + if (binaryFun!pred(haystack.front, needles[i].front)) + { + continue; + } + } + + // This code executed on failure to match + // Out with this guy, check for the others + uint result = startsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]); + if (result > i) ++result; + return result; + } + + // If execution reaches this point, then the front matches for all + // needle ranges, or a needle element has been matched. + // What we need to do now is iterate, lopping off the front of + // the range and checking if the result is empty, or finding an + // element needle and returning. + // If neither happens, we drop to the end and loop. + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) + { + // Test has passed in the previous loop + return i + 1; + } + else + { + needles[i].popFront(); + if (needles[i].empty) return i + 1; + } + } + } + return 0; +} + +/// Ditto +bool startsWith(alias pred = "a == b", R1, R2)(R1 doesThisStart, R2 withThis) +if (isInputRange!R1 && + isInputRange!R2 && + is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) : bool)) +{ + alias haystack = doesThisStart; + alias needle = withThis; + + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + + //Note: While narrow strings don't have a "true" length, for a narrow string to start with another + //narrow string *of the same type*, it must have *at least* as many code units. + static if ((hasLength!R1 && hasLength!R2) || + (isNarrowString!R1 && isNarrowString!R2 && ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof)) + { + if (haystack.length < needle.length) + return false; + } + + static if (isDefaultPred && isArray!R1 && isArray!R2 && + is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) + { + //Array slice comparison mode + return haystack[0 .. needle.length] == needle; + } + else static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && hasLength!R2) + { + //RA dual indexing mode + foreach (j; 0 .. needle.length) + { + if (!binaryFun!pred(haystack[j], needle[j])) + // not found + return false; + } + // found! + return true; + } + else + { + //Standard input range mode + if (needle.empty) return true; + static if (hasLength!R1 && hasLength!R2) + { + //We have previously checked that haystack.length > needle.length, + //So no need to check haystack.empty during iteration + for ( ; ; haystack.popFront() ) + { + if (!binaryFun!pred(haystack.front, needle.front)) break; + needle.popFront(); + if (needle.empty) return true; + } + } + else + { + for ( ; !haystack.empty ; haystack.popFront() ) + { + if (!binaryFun!pred(haystack.front, needle.front)) break; + needle.popFront(); + if (needle.empty) return true; + } + } + return false; + } +} + +/// Ditto +bool startsWith(alias pred = "a == b", R, E)(R doesThisStart, E withThis) +if (isInputRange!R && + is(typeof(binaryFun!pred(doesThisStart.front, withThis)) : bool)) +{ + return doesThisStart.empty + ? false + : binaryFun!pred(doesThisStart.front, withThis); +} + +/// +@safe unittest +{ + assert(startsWith("abc", "")); + assert(startsWith("abc", "a")); + assert(!startsWith("abc", "b")); + assert(startsWith("abc", 'a', "b") == 1); + assert(startsWith("abc", "b", "a") == 2); + assert(startsWith("abc", "a", "a") == 1); + assert(startsWith("abc", "ab", "a") == 2); + assert(startsWith("abc", "x", "a", "b") == 2); + assert(startsWith("abc", "x", "aa", "ab") == 3); + assert(startsWith("abc", "x", "aaa", "sab") == 0); + assert(startsWith("abc", "x", "aaa", "a", "sab") == 3); + alias C = Tuple!(int, "x", int, "y"); + assert(startsWith!"a.x == b"([ C(1,1), C(1,2), C(2,2) ], [1, 1])); + assert(startsWith!"a.x == b"([ C(1,1), C(2,1), C(2,2) ], [1, 1], [1, 2], [1, 3]) == 2); +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.conv : to; + import std.range; + import std.typetuple : TypeTuple; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) + { + assert(!startsWith(to!S("abc"), 'c')); + assert(startsWith(to!S("abc"), 'a', 'c') == 1); + assert(!startsWith(to!S("abc"), 'x', 'n', 'b')); + assert(startsWith(to!S("abc"), 'x', 'n', 'a') == 3); + assert(startsWith(to!S("\uFF28abc"), 'a', '\uFF28', 'c') == 2); + + foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + //Lots of strings + assert(startsWith(to!S("abc"), to!T(""))); + assert(startsWith(to!S("ab"), to!T("a"))); + assert(startsWith(to!S("abc"), to!T("a"))); + assert(!startsWith(to!S("abc"), to!T("b"))); + assert(!startsWith(to!S("abc"), to!T("b"), "bc", "abcd", "xyz")); + assert(startsWith(to!S("abc"), to!T("ab"), 'a') == 2); + assert(startsWith(to!S("abc"), to!T("a"), "b") == 1); + assert(startsWith(to!S("abc"), to!T("b"), "a") == 2); + assert(startsWith(to!S("abc"), to!T("a"), 'a') == 1); + assert(startsWith(to!S("abc"), 'a', to!T("a")) == 1); + assert(startsWith(to!S("abc"), to!T("x"), "a", "b") == 2); + assert(startsWith(to!S("abc"), to!T("x"), "aa", "ab") == 3); + assert(startsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0); + assert(startsWith(to!S("abc"), 'a')); + assert(!startsWith(to!S("abc"), to!T("sab"))); + assert(startsWith(to!S("abc"), 'x', to!T("aaa"), 'a', "sab") == 3); + + //Unicode + assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("\uFF28el"))); + assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("Hel"), to!T("\uFF28el")) == 2); + assert(startsWith(to!S("日本語"), to!T("日本"))); + assert(startsWith(to!S("日本語"), to!T("日本語"))); + assert(!startsWith(to!S("日本"), to!T("日本語"))); + + //Empty + assert(startsWith(to!S(""), T.init)); + assert(!startsWith(to!S(""), 'a')); + assert(startsWith(to!S("a"), T.init)); + assert(startsWith(to!S("a"), T.init, "") == 1); + assert(startsWith(to!S("a"), T.init, 'a') == 1); + assert(startsWith(to!S("a"), 'a', T.init) == 2); + }(); + } + + //Length but no RA + assert(!startsWith("abc".takeExactly(3), "abcd".takeExactly(4))); + assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(3))); + assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(1))); + + foreach (T; TypeTuple!(int, short)) + { + immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; + + //RA range + assert(startsWith(arr, cast(int[])null)); + assert(!startsWith(arr, 5)); + assert(!startsWith(arr, 1)); + assert(startsWith(arr, 0)); + assert(startsWith(arr, 5, 0, 1) == 2); + assert(startsWith(arr, [0])); + assert(startsWith(arr, [0, 1])); + assert(startsWith(arr, [0, 1], 7) == 1); + assert(!startsWith(arr, [0, 1, 7])); + assert(startsWith(arr, [0, 1, 7], [0, 1, 2]) == 2); + + //Normal input range + assert(!startsWith(filter!"true"(arr), 1)); + assert(startsWith(filter!"true"(arr), 0)); + assert(startsWith(filter!"true"(arr), [0])); + assert(startsWith(filter!"true"(arr), [0, 1])); + assert(startsWith(filter!"true"(arr), [0, 1], 7) == 1); + assert(!startsWith(filter!"true"(arr), [0, 1, 7])); + assert(startsWith(filter!"true"(arr), [0, 1, 7], [0, 1, 2]) == 2); + assert(startsWith(arr, filter!"true"([0, 1]))); + assert(startsWith(arr, filter!"true"([0, 1]), 7) == 1); + assert(!startsWith(arr, filter!"true"([0, 1, 7]))); + assert(startsWith(arr, [0, 1, 7], filter!"true"([0, 1, 2])) == 2); + + //Non-default pred + assert(startsWith!("a%10 == b%10")(arr, [10, 11])); + assert(!startsWith!("a%10 == b%10")(arr, [10, 12])); + } +} + +/* (Not yet documented.) +Consume all elements from $(D r) that are equal to one of the elements +$(D es). + */ +void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es) +//if (is(typeof(binaryFun!pred(r1.front, es[0])))) +{ + loop: + for (; !r.empty; r.popFront()) + { + foreach (i, E; Es) + { + if (binaryFun!pred(r.front, es[i])) + { + continue loop; + } + } + break; + } +} + +@safe unittest +{ + //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); + auto s1 = "Hello world"; + skipAll(s1, 'H', 'e'); + assert(s1 == "llo world"); +} + +/** +Interval option specifier for $(D until) (below) and others. + */ +enum OpenRight +{ + no, /// Interval is closed to the right (last element included) + yes /// Interval is open to the right (last element is not included) +} + +struct Until(alias pred, Range, Sentinel) if (isInputRange!Range) +{ + private Range _input; + static if (!is(Sentinel == void)) + private Sentinel _sentinel; + // mixin(bitfields!( + // OpenRight, "_openRight", 1, + // bool, "_done", 1, + // uint, "", 6)); + // OpenRight, "_openRight", 1, + // bool, "_done", 1, + OpenRight _openRight; + bool _done; + + static if (!is(Sentinel == void)) + this(Range input, Sentinel sentinel, + OpenRight openRight = OpenRight.yes) + { + _input = input; + _sentinel = sentinel; + _openRight = openRight; + _done = _input.empty || openRight && predSatisfied(); + } + else + this(Range input, OpenRight openRight = OpenRight.yes) + { + _input = input; + _openRight = openRight; + _done = _input.empty || openRight && predSatisfied(); + } + + @property bool empty() + { + return _done; + } + + @property auto ref front() + { + assert(!empty); + return _input.front; + } + + private bool predSatisfied() + { + static if (is(Sentinel == void)) + return cast(bool) unaryFun!pred(_input.front); + else + return cast(bool) startsWith!pred(_input, _sentinel); + } + + void popFront() + { + assert(!empty); + if (!_openRight) + { + _done = predSatisfied(); + _input.popFront(); + _done = _done || _input.empty; + } + else + { + _input.popFront(); + _done = _input.empty || predSatisfied(); + } + } + + static if (isForwardRange!Range) + { + static if (!is(Sentinel == void)) + @property Until save() + { + Until result = this; + result._input = _input.save; + result._sentinel = _sentinel; + result._openRight = _openRight; + result._done = _done; + return result; + } + else + @property Until save() + { + Until result = this; + result._input = _input.save; + result._openRight = _openRight; + result._done = _done; + return result; + } + } +} + +/** +Lazily iterates $(D range) _until the element $(D e) for which +$(D pred(e, sentinel)) is true. + +Params: + pred = Predicate to determine when to stop. + range = The $(XREF2 range, isInputRange, input range) to iterate over. + sentinel = The element to stop at. + openRight = Determines whether the element for which the given predicate is + true should be included in the resulting range ($(D OpenRight.no)), or + not ($(D OpenRight.yes)). + +Returns: + An $(XREF2 range, isInputRange, input range) that iterates over the + original range's elements, but ends when the specified predicate becomes + true. If the original range is a $(XREF2 range, isForwardRange, forward + range) or higher, this range will be a forward range. + */ +Until!(pred, Range, Sentinel) +until(alias pred = "a == b", Range, Sentinel) +(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes) +if (!is(Sentinel == OpenRight)) +{ + return typeof(return)(range, sentinel, openRight); +} + +/// Ditto +Until!(pred, Range, void) +until(alias pred, Range) +(Range range, OpenRight openRight = OpenRight.yes) +{ + return typeof(return)(range, openRight); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; + assert(equal(a.until(7), [1, 2, 4][])); + assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); + int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; + + static assert(isForwardRange!(typeof(a.until(7)))); + static assert(isForwardRange!(typeof(until!"a == 2"(a, OpenRight.no)))); + + assert(equal(a.until(7), [1, 2, 4][])); + assert(equal(a.until([7, 2]), [1, 2, 4, 7][])); + assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][])); + assert(equal(until!"a == 2"(a, OpenRight.no), [1, 2][])); +} + +unittest // bugzilla 13171 +{ + import std.algorithm.comparison : equal; + import std.range; + auto a = [1, 2, 3, 4]; + assert(equal(refRange(&a).until(3, OpenRight.no), [1, 2, 3])); + assert(a == [4]); +} + +@safe unittest // Issue 10460 +{ + import std.algorithm.comparison : equal; + auto a = [1, 2, 3, 4]; + foreach (ref e; a.until(3)) + e = 0; + assert(equal(a, [0, 0, 3, 4])); +} + +unittest // Issue 13124 +{ + import std.algorithm.comparison : among; + auto s = "hello how\nare you"; + s.until!(c => c.among!('\n', '\r')); +} + diff --git a/std/algorithm/setops.d b/std/algorithm/setops.d new file mode 100644 index 00000000000..be358736abf --- /dev/null +++ b/std/algorithm/setops.d @@ -0,0 +1,1323 @@ +// Written in the D programming language. +/** +This is a submodule of $(LINK2 std_algorithm_package.html, std.algorithm). +It contains generic algorithms that implement set operations. + +$(BOOKTABLE Cheat Sheet, + +$(TR $(TH Function Name) $(TH Description)) + +$(T2 cartesianProduct, + Computes Cartesian product of two ranges.) +$(T2 largestPartialIntersection, + Copies out the values that occur most frequently in a range of ranges.) +$(T2 largestPartialIntersectionWeighted, + Copies out the values that occur most frequently (multiplied by + per-value weights) in a range of ranges.) +$(T2 nWayUnion, + Computes the union of a set of sets implemented as a range of sorted + ranges.) +$(T2 setDifference, + Lazily computes the set difference of two or more sorted ranges.) +$(T2 setIntersection, + Lazily computes the intersection of two or more sorted ranges.) +$(T2 setSymmetricDifference, + Lazily computes the symmetric set difference of two or more sorted + ranges.) +$(T2 setUnion, + Lazily computes the set union of two or more sorted ranges.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_setops.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.setops; + +import std.range.primitives; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.traits; +// FIXME +import std.typetuple; // : TypeTuple, staticMap, allSatisfy, anySatisfy; + +// cartesianProduct +/** +Lazily computes the Cartesian product of two or more ranges. The product is a +_range of tuples of elements from each respective range. + +The conditions for the two-range case are as follows: + +If both ranges are finite, then one must be (at least) a forward range and the +other an input range. + +If one _range is infinite and the other finite, then the finite _range must +be a forward _range, and the infinite range can be an input _range. + +If both ranges are infinite, then both must be forward ranges. + +When there are more than two ranges, the above conditions apply to each +adjacent pair of ranges. +*/ +auto cartesianProduct(R1, R2)(R1 range1, R2 range2) + if (!allSatisfy!(isForwardRange, R1, R2) || + anySatisfy!(isInfinite, R1, R2)) +{ + import std.algorithm.iteration : map, joiner; + + static if (isInfinite!R1 && isInfinite!R2) + { + static if (isForwardRange!R1 && isForwardRange!R2) + { + import std.range : zip, repeat, take, chain, sequence; + + // This algorithm traverses the cartesian product by alternately + // covering the right and bottom edges of an increasing square area + // over the infinite table of combinations. This schedule allows us + // to require only forward ranges. + return zip(sequence!"n"(cast(size_t)0), range1.save, range2.save, + repeat(range1), repeat(range2)) + .map!(function(a) => chain( + zip(repeat(a[1]), take(a[4].save, a[0])), + zip(take(a[3].save, a[0]+1), repeat(a[2])) + ))() + .joiner(); + } + else static assert(0, "cartesianProduct of infinite ranges requires "~ + "forward ranges"); + } + else static if (isInputRange!R1 && isForwardRange!R2 && !isInfinite!R2) + { + import std.range : zip, repeat; + return joiner(map!((ElementType!R1 a) => zip(repeat(a), range2.save)) + (range1)); + } + else static if (isInputRange!R2 && isForwardRange!R1 && !isInfinite!R1) + { + import std.range : zip, repeat; + return joiner(map!((ElementType!R2 a) => zip(range1.save, repeat(a))) + (range2)); + } + else static assert(0, "cartesianProduct involving finite ranges must "~ + "have at least one finite forward range"); +} + +/// +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple; + + auto N = sequence!"n"(0); // the range of natural numbers + auto N2 = cartesianProduct(N, N); // the range of all pairs of natural numbers + + // Various arbitrary number pairs can be found in the range in finite time. + assert(canFind(N2, tuple(0, 0))); + assert(canFind(N2, tuple(123, 321))); + assert(canFind(N2, tuple(11, 35))); + assert(canFind(N2, tuple(279, 172))); +} + +/// +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.typecons : tuple; + + auto B = [ 1, 2, 3 ]; + auto C = [ 4, 5, 6 ]; + auto BC = cartesianProduct(B, C); + + foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], + [2, 6], [3, 6]]) + { + assert(canFind(BC, tuple(n[0], n[1]))); + } +} + +@safe unittest +{ + // Test cartesian product of two infinite ranges + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple; + + auto Even = sequence!"2*n"(0); + auto Odd = sequence!"2*n+1"(0); + auto EvenOdd = cartesianProduct(Even, Odd); + + foreach (pair; [[0, 1], [2, 1], [0, 3], [2, 3], [4, 1], [4, 3], [0, 5], + [2, 5], [4, 5], [6, 1], [6, 3], [6, 5]]) + { + assert(canFind(EvenOdd, tuple(pair[0], pair[1]))); + } + + // This should terminate in finite time + assert(canFind(EvenOdd, tuple(124, 73))); + assert(canFind(EvenOdd, tuple(0, 97))); + assert(canFind(EvenOdd, tuple(42, 1))); +} + +@safe unittest +{ + // Test cartesian product of an infinite input range and a finite forward + // range. + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple; + + auto N = sequence!"n"(0); + auto M = [100, 200, 300]; + auto NM = cartesianProduct(N,M); + + foreach (pair; [[0, 100], [0, 200], [0, 300], [1, 100], [1, 200], [1, 300], + [2, 100], [2, 200], [2, 300], [3, 100], [3, 200], + [3, 300]]) + { + assert(canFind(NM, tuple(pair[0], pair[1]))); + } + + // We can't solve the halting problem, so we can only check a finite + // initial segment here. + assert(!canFind(NM.take(100), tuple(100, 0))); + assert(!canFind(NM.take(100), tuple(1, 1))); + assert(!canFind(NM.take(100), tuple(100, 200))); + + auto MN = cartesianProduct(M,N); + foreach (pair; [[100, 0], [200, 0], [300, 0], [100, 1], [200, 1], [300, 1], + [100, 2], [200, 2], [300, 2], [100, 3], [200, 3], + [300, 3]]) + { + assert(canFind(MN, tuple(pair[0], pair[1]))); + } + + // We can't solve the halting problem, so we can only check a finite + // initial segment here. + assert(!canFind(MN.take(100), tuple(0, 100))); + assert(!canFind(MN.take(100), tuple(0, 1))); + assert(!canFind(MN.take(100), tuple(100, 200))); +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.typecons : tuple; + + // Test cartesian product of two finite ranges. + auto X = [1, 2, 3]; + auto Y = [4, 5, 6]; + auto XY = cartesianProduct(X, Y); + auto Expected = [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4], + [3, 5], [3, 6]]; + + // Verify Expected ⊆ XY + foreach (pair; Expected) + { + assert(canFind(XY, tuple(pair[0], pair[1]))); + } + + // Verify XY ⊆ Expected + foreach (pair; XY) + { + assert(canFind(Expected, [pair[0], pair[1]])); + } + + // And therefore, by set comprehension, XY == Expected +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.typecons : tuple; + + import std.range; + auto N = sequence!"n"(0); + + // To force the template to fall to the second case, we wrap N in a struct + // that doesn't allow bidirectional access. + struct FwdRangeWrapper(R) + { + R impl; + + // Input range API + @property auto front() { return impl.front; } + void popFront() { impl.popFront(); } + static if (isInfinite!R) + enum empty = false; + else + @property bool empty() { return impl.empty; } + + // Forward range API + @property auto save() { return typeof(this)(impl.save); } + } + auto fwdWrap(R)(R range) { return FwdRangeWrapper!R(range); } + + // General test: two infinite bidirectional ranges + auto N2 = cartesianProduct(N, N); + + assert(canFind(N2, tuple(0, 0))); + assert(canFind(N2, tuple(123, 321))); + assert(canFind(N2, tuple(11, 35))); + assert(canFind(N2, tuple(279, 172))); + + // Test first case: forward range with bidirectional range + auto fwdN = fwdWrap(N); + auto N2_a = cartesianProduct(fwdN, N); + + assert(canFind(N2_a, tuple(0, 0))); + assert(canFind(N2_a, tuple(123, 321))); + assert(canFind(N2_a, tuple(11, 35))); + assert(canFind(N2_a, tuple(279, 172))); + + // Test second case: bidirectional range with forward range + auto N2_b = cartesianProduct(N, fwdN); + + assert(canFind(N2_b, tuple(0, 0))); + assert(canFind(N2_b, tuple(123, 321))); + assert(canFind(N2_b, tuple(11, 35))); + assert(canFind(N2_b, tuple(279, 172))); + + // Test third case: finite forward range with (infinite) input range + static struct InpRangeWrapper(R) + { + R impl; + + // Input range API + @property auto front() { return impl.front; } + void popFront() { impl.popFront(); } + static if (isInfinite!R) + enum empty = false; + else + @property bool empty() { return impl.empty; } + } + auto inpWrap(R)(R r) { return InpRangeWrapper!R(r); } + + auto inpN = inpWrap(N); + auto B = [ 1, 2, 3 ]; + auto fwdB = fwdWrap(B); + auto BN = cartesianProduct(fwdB, inpN); + + assert(equal(map!"[a[0],a[1]]"(BN.take(10)), [[1, 0], [2, 0], [3, 0], + [1, 1], [2, 1], [3, 1], [1, 2], [2, 2], [3, 2], [1, 3]])); + + // Test fourth case: (infinite) input range with finite forward range + auto NB = cartesianProduct(inpN, fwdB); + + assert(equal(map!"[a[0],a[1]]"(NB.take(10)), [[0, 1], [0, 2], [0, 3], + [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1]])); + + // General finite range case + auto C = [ 4, 5, 6 ]; + auto BC = cartesianProduct(B, C); + + foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], + [2, 6], [3, 6]]) + { + assert(canFind(BC, tuple(n[0], n[1]))); + } +} + +// Issue 13091 +pure nothrow @safe @nogc unittest +{ + import std.algorithm: cartesianProduct; + int[1] a = [1]; + foreach (t; cartesianProduct(a[], a[])) {} +} + +/// ditto +auto cartesianProduct(RR...)(RR ranges) + if (ranges.length >= 2 && + allSatisfy!(isForwardRange, RR) && + !anySatisfy!(isInfinite, RR)) +{ + // This overload uses a much less template-heavy implementation when + // all ranges are finite forward ranges, which is the most common use + // case, so that we don't run out of resources too quickly. + // + // For infinite ranges or non-forward ranges, we fall back to the old + // implementation which expands an exponential number of templates. + import std.typecons : tuple; + + static struct Result + { + RR ranges; + RR current; + bool empty = true; + + this(RR _ranges) + { + ranges = _ranges; + empty = false; + foreach (i, r; ranges) + { + current[i] = r.save; + if (current[i].empty) + empty = true; + } + } + @property auto front() + { + import std.algorithm : algoFormat; // FIXME + import std.range : iota; + return mixin(algoFormat("tuple(%(current[%d].front%|,%))", + iota(0, current.length))); + } + void popFront() + { + foreach_reverse (i, ref r; current) + { + r.popFront(); + if (!r.empty) break; + + static if (i==0) + empty = true; + else + r = ranges[i].save; // rollover + } + } + @property Result save() + { + Result copy = this; + foreach (i, r; ranges) + { + copy.ranges[i] = r.save; + copy.current[i] = current[i].save; + } + return copy; + } + } + static assert(isForwardRange!Result); + + return Result(ranges); +} + +@safe unittest +{ + // Issue 10693: cartesian product of empty ranges should be empty. + int[] a, b, c, d, e; + auto cprod = cartesianProduct(a,b,c,d,e); + assert(cprod.empty); + foreach (_; cprod) {} // should not crash + + // Test case where only one of the ranges is empty: the result should still + // be empty. + int[] p=[1], q=[]; + auto cprod2 = cartesianProduct(p,p,p,q,p); + assert(cprod2.empty); + foreach (_; cprod2) {} // should not crash +} + +@safe unittest +{ + // .init value of cartesianProduct should be empty + auto cprod = cartesianProduct([0,0], [1,1], [2,2]); + assert(!cprod.empty); + assert(cprod.init.empty); +} + +@safe unittest +{ + // Issue 13393 + assert(!cartesianProduct([0],[0],[0]).save.empty); +} + +/// ditto +auto cartesianProduct(R1, R2, RR...)(R1 range1, R2 range2, RR otherRanges) + if (!allSatisfy!(isForwardRange, R1, R2, RR) || + anySatisfy!(isInfinite, R1, R2, RR)) +{ + /* We implement the n-ary cartesian product by recursively invoking the + * binary cartesian product. To make the resulting range nicer, we denest + * one level of tuples so that a ternary cartesian product, for example, + * returns 3-element tuples instead of nested 2-element tuples. + */ + import std.algorithm : algoFormat; // FIXME + import std.algorithm.iteration : map; + import std.range : iota; + + enum string denest = algoFormat("tuple(a[0], %(a[1][%d]%|,%))", + iota(0, otherRanges.length+1)); + return map!denest( + cartesianProduct(range1, cartesianProduct(range2, otherRanges)) + ); +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple, Tuple; + + auto N = sequence!"n"(0); + auto N3 = cartesianProduct(N, N, N); + + // Check that tuples are properly denested + assert(is(ElementType!(typeof(N3)) == Tuple!(size_t,size_t,size_t))); + + assert(canFind(N3, tuple(0, 27, 7))); + assert(canFind(N3, tuple(50, 23, 71))); + assert(canFind(N3, tuple(9, 3, 0))); +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple, Tuple; + + auto N = sequence!"n"(0); + auto N4 = cartesianProduct(N, N, N, N); + + // Check that tuples are properly denested + assert(is(ElementType!(typeof(N4)) == Tuple!(size_t,size_t,size_t,size_t))); + + assert(canFind(N4, tuple(1, 2, 3, 4))); + assert(canFind(N4, tuple(4, 3, 2, 1))); + assert(canFind(N4, tuple(10, 31, 7, 12))); +} + +// Issue 9878 +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + auto A = [ 1, 2, 3 ]; + auto B = [ 'a', 'b', 'c' ]; + auto C = [ "x", "y", "z" ]; + auto ABC = cartesianProduct(A, B, C); + + assert(ABC.equal([ + tuple(1, 'a', "x"), tuple(1, 'a', "y"), tuple(1, 'a', "z"), + tuple(1, 'b', "x"), tuple(1, 'b', "y"), tuple(1, 'b', "z"), + tuple(1, 'c', "x"), tuple(1, 'c', "y"), tuple(1, 'c', "z"), + tuple(2, 'a', "x"), tuple(2, 'a', "y"), tuple(2, 'a', "z"), + tuple(2, 'b', "x"), tuple(2, 'b', "y"), tuple(2, 'b', "z"), + tuple(2, 'c', "x"), tuple(2, 'c', "y"), tuple(2, 'c', "z"), + tuple(3, 'a', "x"), tuple(3, 'a', "y"), tuple(3, 'a', "z"), + tuple(3, 'b', "x"), tuple(3, 'b', "y"), tuple(3, 'b', "z"), + tuple(3, 'c', "x"), tuple(3, 'c', "y"), tuple(3, 'c', "z") + ])); +} + +pure @safe nothrow @nogc unittest +{ + int[2] A = [1,2]; + auto C = cartesianProduct(A[], A[], A[]); + assert(isForwardRange!(typeof(C))); + + C.popFront(); + auto front1 = C.front; + auto D = C.save; + C.popFront(); + assert(D.front == front1); +} + +// Issue 13935 +unittest +{ + import std.algorithm.iteration : map; + auto seq = [1, 2].map!(x => x); + foreach (pair; cartesianProduct(seq, seq)) {} +} + +// largestPartialIntersection +/** +Given a range of sorted forward ranges $(D ror), copies to $(D tgt) +the elements that are common to most ranges, along with their number +of occurrences. All ranges in $(D ror) are assumed to be sorted by $(D +less). Only the most frequent $(D tgt.length) elements are returned. + +Example: +---- +// Figure which number can be found in most arrays of the set of +// arrays below. +double[][] a = +[ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], +]; +auto b = new Tuple!(double, uint)[1]; +largestPartialIntersection(a, b); +// First member is the item, second is the occurrence count +assert(b[0] == tuple(7.0, 4u)); +---- + +$(D 7.0) is the correct answer because it occurs in $(D 4) out of the +$(D 5) inputs, more than any other number. The second member of the +resulting tuple is indeed $(D 4) (recording the number of occurrences +of $(D 7.0)). If more of the top-frequent numbers are needed, just +create a larger $(D tgt) range. In the example above, creating $(D b) +with length $(D 2) yields $(D tuple(1.0, 3u)) in the second position. + +The function $(D largestPartialIntersection) is useful for +e.g. searching an $(LUCKY inverted index) for the documents most +likely to contain some terms of interest. The complexity of the search +is $(BIGOH n * log(tgt.length)), where $(D n) is the sum of lengths of +all input ranges. This approach is faster than keeping an associative +array of the occurrences and then selecting its top items, and also +requires less memory ($(D largestPartialIntersection) builds its +result directly in $(D tgt) and requires no extra memory). + +Warning: Because $(D largestPartialIntersection) does not allocate +extra memory, it will leave $(D ror) modified. Namely, $(D +largestPartialIntersection) assumes ownership of $(D ror) and +discretionarily swaps and advances elements of it. If you want $(D +ror) to preserve its contents after the call, you may want to pass a +duplicate to $(D largestPartialIntersection) (and perhaps cache the +duplicate in between calls). + */ +void largestPartialIntersection +(alias less = "a < b", RangeOfRanges, Range) +(RangeOfRanges ror, Range tgt, SortOutput sorted = SortOutput.no) +{ + struct UnitWeights + { + static int opIndex(ElementType!(ElementType!RangeOfRanges)) { return 1; } + } + return largestPartialIntersectionWeighted!less(ror, tgt, UnitWeights(), + sorted); +} + +import std.algorithm : SortOutput; // FIXME + +// largestPartialIntersectionWeighted +/** +Similar to $(D largestPartialIntersection), but associates a weight +with each distinct element in the intersection. + +Example: +---- +// Figure which number can be found in most arrays of the set of +// arrays below, with specific per-element weights +double[][] a = +[ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], +]; +auto b = new Tuple!(double, uint)[1]; +double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; +largestPartialIntersectionWeighted(a, b, weights); +// First member is the item, second is the occurrence count +assert(b[0] == tuple(4.0, 2u)); +---- + +The correct answer in this case is $(D 4.0), which, although only +appears two times, has a total weight $(D 4.6) (three times its weight +$(D 2.3)). The value $(D 7) is weighted with $(D 1.1) and occurs four +times for a total weight $(D 4.4). + */ +void largestPartialIntersectionWeighted +(alias less = "a < b", RangeOfRanges, Range, WeightsAA) +(RangeOfRanges ror, Range tgt, WeightsAA weights, SortOutput sorted = SortOutput.no) +{ + import std.algorithm.iteration : group; + import std.algorithm.sorting : topNCopy; + + if (tgt.empty) return; + alias InfoType = ElementType!Range; + bool heapComp(InfoType a, InfoType b) + { + return weights[a[0]] * a[1] > weights[b[0]] * b[1]; + } + topNCopy!heapComp(group(nWayUnion!less(ror)), tgt, sorted); +} + +unittest +{ + import std.conv : text; + import std.typecons : tuple, Tuple; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto b = new Tuple!(double, uint)[2]; + largestPartialIntersection(a, b, SortOutput.yes); + //sort(b); + //writeln(b); + assert(b == [ tuple(7.0, 4u), tuple(1.0, 3u) ][], text(b)); + assert(a[0].empty); +} + +unittest +{ + import std.conv : text; + import std.typecons : tuple, Tuple; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + string[][] a = + [ + [ "1", "4", "7", "8" ], + [ "1", "7" ], + [ "1", "7", "8"], + [ "4" ], + [ "7" ], + ]; + auto b = new Tuple!(string, uint)[2]; + largestPartialIntersection(a, b, SortOutput.yes); + //writeln(b); + assert(b == [ tuple("7", 4u), tuple("1", 3u) ][], text(b)); +} + +unittest +{ + import std.typecons : tuple, Tuple; + + //scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); +// Figure which number can be found in most arrays of the set of +// arrays below, with specific per-element weights + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto b = new Tuple!(double, uint)[1]; + double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; + largestPartialIntersectionWeighted(a, b, weights); +// First member is the item, second is the occurrence count + //writeln(b[0]); + assert(b[0] == tuple(4.0, 2u)); +} + +unittest +{ + import std.container : Array; + import std.typecons : Tuple; + + alias T = Tuple!(uint, uint); + const Array!T arrayOne = Array!T( [ T(1,2), T(3,4) ] ); + const Array!T arrayTwo = Array!T([ T(1,2), T(3,4) ] ); + + assert(arrayOne == arrayTwo); +} + +// NWayUnion +/** +Computes the union of multiple sets. The input sets are passed as a +range of ranges and each is assumed to be sorted by $(D +less). Computation is done lazily, one union element at a time. The +complexity of one $(D popFront) operation is $(BIGOH +log(ror.length)). However, the length of $(D ror) decreases as ranges +in it are exhausted, so the complexity of a full pass through $(D +NWayUnion) is dependent on the distribution of the lengths of ranges +contained within $(D ror). If all ranges have the same length $(D n) +(worst case scenario), the complexity of a full pass through $(D +NWayUnion) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D +log(ror.length)) times worse than just spanning all ranges in +turn. The output comes sorted (unstably) by $(D less). + +Warning: Because $(D NWayUnion) does not allocate extra memory, it +will leave $(D ror) modified. Namely, $(D NWayUnion) assumes ownership +of $(D ror) and discretionarily swaps and advances elements of it. If +you want $(D ror) to preserve its contents after the call, you may +want to pass a duplicate to $(D NWayUnion) (and perhaps cache the +duplicate in between calls). + */ +struct NWayUnion(alias less, RangeOfRanges) +{ + import std.container : BinaryHeap; + + private alias ElementType = .ElementType!(.ElementType!RangeOfRanges); + private alias comp = binaryFun!less; + private RangeOfRanges _ror; + static bool compFront(.ElementType!RangeOfRanges a, + .ElementType!RangeOfRanges b) + { + // revert comparison order so we get the smallest elements first + return comp(b.front, a.front); + } + BinaryHeap!(RangeOfRanges, compFront) _heap; + + this(RangeOfRanges ror) + { + import std.algorithm.mutation : remove, SwapStrategy; + + // Preemptively get rid of all empty ranges in the input + // No need for stability either + _ror = remove!("a.empty", SwapStrategy.unstable)(ror); + //Build the heap across the range + _heap.acquire(_ror); + } + + @property bool empty() { return _ror.empty; } + + @property auto ref front() + { + return _heap.front.front; + } + + void popFront() + { + _heap.removeFront(); + // let's look at the guy just popped + _ror.back.popFront(); + if (_ror.back.empty) + { + _ror.popBack(); + // nothing else to do: the empty range is not in the + // heap and not in _ror + return; + } + // Put the popped range back in the heap + _heap.conditionalInsert(_ror.back) || assert(false); + } +} + +/// Ditto +NWayUnion!(less, RangeOfRanges) nWayUnion +(alias less = "a < b", RangeOfRanges) +(RangeOfRanges ror) +{ + return typeof(return)(ror); +} + +/// +unittest +{ + import std.algorithm.comparison : equal; + + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto witness = [ + 1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8 + ]; + assert(equal(nWayUnion(a), witness)); +} + +/** +Lazily computes the difference of $(D r1) and $(D r2). The two ranges +are assumed to be sorted by $(D less). The element types of the two +ranges must have a common type. + */ +struct SetDifference(alias less = "a < b", R1, R2) + if (isInputRange!(R1) && isInputRange!(R2)) +{ +private: + R1 r1; + R2 r2; + alias comp = binaryFun!(less); + + void adjustPosition() + { + while (!r1.empty) + { + if (r2.empty || comp(r1.front, r2.front)) break; + if (comp(r2.front, r1.front)) + { + r2.popFront(); + } + else + { + // both are equal + r1.popFront(); + r2.popFront(); + } + } + } + +public: + this(R1 r1, R2 r2) + { + this.r1 = r1; + this.r2 = r2; + // position to the first element + adjustPosition(); + } + + void popFront() + { + r1.popFront(); + adjustPosition(); + } + + @property auto ref front() + { + assert(!empty); + return r1.front; + } + + static if (isForwardRange!R1 && isForwardRange!R2) + { + @property typeof(this) save() + { + auto ret = this; + ret.r1 = r1.save; + ret.r2 = r2.save; + return ret; + } + } + + @property bool empty() { return r1.empty; } +} + +/// Ditto +SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) +(R1 r1, R2 r2) +{ + return typeof(return)(r1, r2); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + assert(equal(setDifference(a, b), [5, 9][])); + static assert(isForwardRange!(typeof(setDifference(a, b)))); +} + +@safe unittest // Issue 10460 +{ + import std.algorithm.comparison : equal; + + int[] a = [1, 2, 3, 4, 5]; + int[] b = [2, 4]; + foreach (ref e; setDifference(a, b)) + e = 0; + assert(equal(a, [0, 2, 0, 4, 0])); +} + +/** +Lazily computes the intersection of two or more input ranges $(D +ranges). The ranges are assumed to be sorted by $(D less). The element +types of the ranges must have a common type. + */ +struct SetIntersection(alias less = "a < b", Rs...) + if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && + !is(CommonType!(staticMap!(ElementType, Rs)) == void)) +{ +private: + Rs _input; + alias comp = binaryFun!less; + alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); + + // Positions to the first elements that are all equal + void adjustPosition() + { + if (empty) return; + + size_t done = Rs.length; + static if (Rs.length > 1) while (true) + { + foreach (i, ref r; _input) + { + alias next = _input[(i + 1) % Rs.length]; + + if (comp(next.front, r.front)) + { + do { + next.popFront(); + if (next.empty) return; + } while(comp(next.front, r.front)); + done = Rs.length; + } + if (--done == 0) return; + } + } + } + +public: + this(Rs input) + { + this._input = input; + // position to the first element + adjustPosition(); + } + + @property bool empty() + { + foreach (ref r; _input) + { + if (r.empty) return true; + } + return false; + } + + void popFront() + { + assert(!empty); + static if (Rs.length > 1) foreach (i, ref r; _input) + { + alias next = _input[(i + 1) % Rs.length]; + assert(!comp(r.front, next.front)); + } + + foreach (ref r; _input) + { + r.popFront(); + } + adjustPosition(); + } + + @property ElementType front() + { + assert(!empty); + return _input[0].front; + } + + static if (allSatisfy!(isForwardRange, Rs)) + { + @property SetIntersection save() + { + auto ret = this; + foreach (i, ref r; _input) + { + ret._input[i] = r.save; + } + return ret; + } + } +} + +/// Ditto +SetIntersection!(less, Rs) setIntersection(alias less = "a < b", Rs...)(Rs ranges) + if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && + !is(CommonType!(staticMap!(ElementType, Rs)) == void)) +{ + return typeof(return)(ranges); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + int[] c = [ 0, 1, 4, 5, 7, 8 ]; + assert(equal(setIntersection(a, a), a)); + assert(equal(setIntersection(a, b), [1, 2, 4, 7])); + assert(equal(setIntersection(a, b, c), [1, 4, 7])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + int[] c = [ 0, 1, 4, 5, 7, 8 ]; + int[] d = [ 1, 3, 4 ]; + int[] e = [ 4, 5 ]; + + assert(equal(setIntersection(a, a), a)); + assert(equal(setIntersection(a, a, a), a)); + assert(equal(setIntersection(a, b), [1, 2, 4, 7])); + assert(equal(setIntersection(a, b, c), [1, 4, 7])); + assert(equal(setIntersection(a, b, c, d), [1, 4])); + assert(equal(setIntersection(a, b, c, d, e), [4])); + + auto inpA = a.filter!(_ => true), inpB = b.filter!(_ => true); + auto inpC = c.filter!(_ => true), inpD = d.filter!(_ => true); + assert(equal(setIntersection(inpA, inpB, inpC, inpD), [1, 4])); + + assert(equal(setIntersection(a, b, b, a), [1, 2, 4, 7])); + assert(equal(setIntersection(a, c, b), [1, 4, 7])); + assert(equal(setIntersection(b, a, c), [1, 4, 7])); + assert(equal(setIntersection(b, c, a), [1, 4, 7])); + assert(equal(setIntersection(c, a, b), [1, 4, 7])); + assert(equal(setIntersection(c, b, a), [1, 4, 7])); +} + +/** +Lazily computes the symmetric difference of $(D r1) and $(D r2), +i.e. the elements that are present in exactly one of $(D r1) and $(D +r2). The two ranges are assumed to be sorted by $(D less), and the +output is also sorted by $(D less). The element types of the two +ranges must have a common type. + +If both arguments are ranges of L-values of the same type then +$(D SetSymmetricDifference) will also be a range of L-values of +that type. + */ +struct SetSymmetricDifference(alias less = "a < b", R1, R2) + if (isInputRange!(R1) && isInputRange!(R2)) +{ +private: + R1 r1; + R2 r2; + //bool usingR2; + alias comp = binaryFun!(less); + + void adjustPosition() + { + while (!r1.empty && !r2.empty) + { + if (comp(r1.front, r2.front) || comp(r2.front, r1.front)) + { + break; + } + // equal, pop both + r1.popFront(); + r2.popFront(); + } + } + +public: + this(R1 r1, R2 r2) + { + this.r1 = r1; + this.r2 = r2; + // position to the first element + adjustPosition(); + } + + void popFront() + { + assert(!empty); + if (r1.empty) r2.popFront(); + else if (r2.empty) r1.popFront(); + else + { + // neither is empty + if (comp(r1.front, r2.front)) + { + r1.popFront(); + } + else + { + assert(comp(r2.front, r1.front)); + r2.popFront(); + } + } + adjustPosition(); + } + + @property auto ref front() + { + assert(!empty); + bool chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front); + assert(chooseR1 || r1.empty || comp(r2.front, r1.front)); + return chooseR1 ? r1.front : r2.front; + } + + static if (isForwardRange!R1 && isForwardRange!R2) + { + @property typeof(this) save() + { + auto ret = this; + ret.r1 = r1.save; + ret.r2 = r2.save; + return ret; + } + } + + ref auto opSlice() { return this; } + + @property bool empty() { return r1.empty && r2.empty; } +} + +/// Ditto +SetSymmetricDifference!(less, R1, R2) +setSymmetricDifference(alias less = "a < b", R1, R2) +(R1 r1, R2 r2) +{ + return typeof(return)(r1, r2); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + assert(equal(setSymmetricDifference(a, b), [0, 5, 8, 9][])); + static assert(isForwardRange!(typeof(setSymmetricDifference(a, b)))); +} + +@safe unittest // Issue 10460 +{ + int[] a = [1, 2]; + double[] b = [2.0, 3.0]; + int[] c = [2, 3]; + + alias R1 = typeof(setSymmetricDifference(a, b)); + static assert(is(ElementType!R1 == double)); + static assert(!hasLvalueElements!R1); + + alias R2 = typeof(setSymmetricDifference(a, c)); + static assert(is(ElementType!R2 == int)); + static assert(hasLvalueElements!R2); +} + +/** +Lazily computes the union of two or more ranges $(D rs). The ranges +are assumed to be sorted by $(D less). Elements in the output are not +unique; the length of the output is the sum of the lengths of the +inputs. (The $(D length) member is offered if all ranges also have +length.) The element types of all ranges must have a common type. + */ +struct SetUnion(alias less = "a < b", Rs...) if (allSatisfy!(isInputRange, Rs)) +{ +private: + Rs _r; + alias comp = binaryFun!(less); + uint _crt; + + void adjustPosition(uint candidate = 0)() + { + static if (candidate == Rs.length) + { + _crt = _crt.max; + } + else + { + if (_r[candidate].empty) + { + adjustPosition!(candidate + 1)(); + return; + } + foreach (i, U; Rs[candidate + 1 .. $]) + { + enum j = candidate + i + 1; + if (_r[j].empty) continue; + if (comp(_r[j].front, _r[candidate].front)) + { + // a new candidate was found + adjustPosition!(j)(); + return; + } + } + // Found a successful candidate + _crt = candidate; + } + } + +public: + alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); + + this(Rs rs) + { + this._r = rs; + adjustPosition(); + } + + @property bool empty() + { + return _crt == _crt.max; + } + + void popFront() + { + // Assumes _crt is correct + assert(!empty); + foreach (i, U; Rs) + { + if (i < _crt) continue; + // found _crt + assert(!_r[i].empty); + _r[i].popFront(); + adjustPosition(); + return; + } + assert(false); + } + + @property ElementType front() + { + assert(!empty); + // Assume _crt is correct + foreach (i, U; Rs) + { + if (i < _crt) continue; + assert(!_r[i].empty); + return _r[i].front; + } + assert(false); + } + + static if (allSatisfy!(isForwardRange, Rs)) + { + @property auto save() + { + auto ret = this; + foreach (ti, elem; _r) + { + ret._r[ti] = elem.save; + } + return ret; + } + } + + static if (allSatisfy!(hasLength, Rs)) + { + @property size_t length() + { + size_t result; + foreach (i, U; Rs) + { + result += _r[i].length; + } + return result; + } + + alias opDollar = length; + } +} + +/// Ditto +SetUnion!(less, Rs) setUnion(alias less = "a < b", Rs...) +(Rs rs) +{ + return typeof(return)(rs); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + int[] c = [ 10 ]; + + assert(setUnion(a, b).length == a.length + b.length); + assert(equal(setUnion(a, b), [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9][])); + assert(equal(setUnion(a, c, b), + [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9, 10][])); + + static assert(isForwardRange!(typeof(setUnion(a, b)))); +} + diff --git a/std/algorithm/sorting.d b/std/algorithm/sorting.d new file mode 100644 index 00000000000..20e06acb6fc --- /dev/null +++ b/std/algorithm/sorting.d @@ -0,0 +1,2731 @@ +// Written in the D programming language. +/** +This is a submodule of $(LINK2 std_algorithm_package.html, std.algorithm). +It contains generic _sorting algorithms. + +$(BOOKTABLE Cheat Sheet, + +$(TR $(TH Function Name) $(TH Description)) + +$(T2 completeSort, + If $(D a = [10, 20, 30]) and $(D b = [40, 6, 15]), then + $(D completeSort(a, b)) leaves $(D a = [6, 10, 15]) and $(D b = [20, + 30, 40]). + The range $(D a) must be sorted prior to the call, and as a result the + combination $(D $(XREF range,chain)(a, b)) is sorted.) +$(T2 isPartitioned, + $(D isPartitioned!"a < 0"([-1, -2, 1, 0, 2])) returns $(D true) because + the predicate is $(D true) for a portion of the range and $(D false) + afterwards.) +$(T2 isSorted, + $(D isSorted([1, 1, 2, 3])) returns $(D true).) +$(T2 makeIndex, + Creates a separate index for a range.) +$(T2 nextEvenPermutation, + Computes the next lexicographically greater even permutation of a range + in-place.) +$(T2 nextPermutation, + Computes the next lexicographically greater permutation of a range + in-place.) +$(T2 partialSort, + If $(D a = [5, 4, 3, 2, 1]), then $(D partialSort(a, 3)) leaves + $(D a[0 .. 3] = [1, 2, 3]). + The other elements of $(D a) are left in an unspecified order.) +$(T2 partition, + Partitions a range according to a predicate.) +$(T2 partition3, + Partitions a range in three parts (less than, equal, greater than the + given pivot).) +$(T2 schwartzSort, + Sorts with the help of the $(LUCKY Schwartzian transform).) +$(T2 sort, + Sorts.) +$(T2 topN, + Separates the top elements in a range.) +$(T2 topNCopy, + Copies out the top elements of a range.) +$(T2 topNIndex, + Builds an index of the top elements of a range.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_sorting.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.sorting; + +import std.algorithm : SortOutput; // FIXME +import std.algorithm.mutation : SwapStrategy; +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +// FIXME +import std.range; // : SortedRange; +import std.traits; + +// completeSort +/** +Sorts the random-access range $(D chain(lhs, rhs)) according to +predicate $(D less). The left-hand side of the range $(D lhs) is +assumed to be already sorted; $(D rhs) is assumed to be unsorted. The +exact strategy chosen depends on the relative sizes of $(D lhs) and +$(D rhs). Performs $(BIGOH lhs.length + rhs.length * log(rhs.length)) +(best case) to $(BIGOH (lhs.length + rhs.length) * log(lhs.length + +rhs.length)) (worst-case) evaluations of $(D swap). +*/ +void completeSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range1, Range2)(SortedRange!(Range1, less) lhs, Range2 rhs) +if (hasLength!(Range2) && hasSlicing!(Range2)) +{ + import std.algorithm : bringToFront; // FIXME + import std.range : chain, assumeSorted; + // Probably this algorithm can be optimized by using in-place + // merge + auto lhsOriginal = lhs.release(); + foreach (i; 0 .. rhs.length) + { + auto sortedSoFar = chain(lhsOriginal, rhs[0 .. i]); + auto ub = assumeSorted!less(sortedSoFar).upperBound(rhs[i]); + if (!ub.length) continue; + bringToFront(ub.release(), rhs[i .. i + 1]); + } +} + +/// +unittest +{ + import std.range : assumeSorted; + int[] a = [ 1, 2, 3 ]; + int[] b = [ 4, 0, 6, 5 ]; + completeSort(assumeSorted(a), b); + assert(a == [ 0, 1, 2 ]); + assert(b == [ 3, 4, 5, 6 ]); +} + +// isSorted +/** +Checks whether a forward range is sorted according to the comparison +operation $(D less). Performs $(BIGOH r.length) evaluations of $(D +less). +*/ +bool isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!(Range)) +{ + if (r.empty) return true; + + static if (isRandomAccessRange!Range && hasLength!Range) + { + immutable limit = r.length - 1; + foreach (i; 0 .. limit) + { + if (!binaryFun!less(r[i + 1], r[i])) continue; + assert( + !binaryFun!less(r[i], r[i + 1]), + "Predicate for isSorted is not antisymmetric. Both" ~ + " pred(a, b) and pred(b, a) are true for certain values."); + return false; + } + } + else + { + auto ahead = r; + ahead.popFront(); + size_t i; + + for (; !ahead.empty; ahead.popFront(), r.popFront(), ++i) + { + if (!binaryFun!less(ahead.front, r.front)) continue; + // Check for antisymmetric predicate + assert( + !binaryFun!less(r.front, ahead.front), + "Predicate for isSorted is not antisymmetric. Both" ~ + " pred(a, b) and pred(b, a) are true for certain values."); + return false; + } + } + return true; +} + +/// +@safe unittest +{ + int[] arr = [4, 3, 2, 1]; + assert(!isSorted(arr)); + sort(arr); + assert(isSorted(arr)); + sort!("a > b")(arr); + assert(isSorted!("a > b")(arr)); +} + +@safe unittest +{ + import std.conv : to; + + // Issue 9457 + auto x = "abcd"; + assert(isSorted(x)); + auto y = "acbd"; + assert(!isSorted(y)); + + int[] a = [1, 2, 3]; + assert(isSorted(a)); + int[] b = [1, 3, 2]; + assert(!isSorted(b)); + + dchar[] ds = "コーヒーが好きです"d.dup; + sort(ds); + string s = to!string(ds); + assert(isSorted(ds)); // random-access + assert(isSorted(s)); // bidirectional +} + +// partition +/** +Partitions a range in two using $(D pred) as a +predicate. Specifically, reorders the range $(D r = [left, +right$(RPAREN)) using $(D swap) such that all elements $(D i) for +which $(D pred(i)) is $(D true) come before all elements $(D j) for +which $(D pred(j)) returns $(D false). + +Performs $(BIGOH r.length) (if unstable or semistable) or $(BIGOH +r.length * log(r.length)) (if stable) evaluations of $(D less) and $(D +swap). The unstable version computes the minimum possible evaluations +of $(D swap) (roughly half of those performed by the semistable +version). + +Returns: + +The right part of $(D r) after partitioning. + +If $(D ss == SwapStrategy.stable), $(D partition) preserves the +relative ordering of all elements $(D a), $(D b) in $(D r) for which +$(D pred(a) == pred(b)). If $(D ss == SwapStrategy.semistable), $(D +partition) preserves the relative ordering of all elements $(D a), $(D +b) in the left part of $(D r) for which $(D pred(a) == pred(b)). + +See_Also: + STL's $(WEB sgi.com/tech/stl/_partition.html, _partition)$(BR) + STL's $(WEB sgi.com/tech/stl/stable_partition.html, stable_partition) +*/ +Range partition(alias predicate, + SwapStrategy ss = SwapStrategy.unstable, Range)(Range r) + if ((ss == SwapStrategy.stable && isRandomAccessRange!(Range)) + || (ss != SwapStrategy.stable && isForwardRange!(Range))) +{ + import std.algorithm : bringToFront, swap; // FIXME; + alias pred = unaryFun!(predicate); + if (r.empty) return r; + static if (ss == SwapStrategy.stable) + { + if (r.length == 1) + { + if (pred(r.front)) r.popFront(); + return r; + } + const middle = r.length / 2; + alias recurse = .partition!(pred, ss, Range); + auto lower = recurse(r[0 .. middle]); + auto upper = recurse(r[middle .. $]); + bringToFront(lower, r[middle .. r.length - upper.length]); + return r[r.length - lower.length - upper.length .. r.length]; + } + else static if (ss == SwapStrategy.semistable) + { + for (; !r.empty; r.popFront()) + { + // skip the initial portion of "correct" elements + if (pred(r.front)) continue; + // hit the first "bad" element + auto result = r; + for (r.popFront(); !r.empty; r.popFront()) + { + if (!pred(r.front)) continue; + swap(result.front, r.front); + result.popFront(); + } + return result; + } + return r; + } + else // ss == SwapStrategy.unstable + { + // Inspired from www.stepanovpapers.com/PAM3-partition_notes.pdf, + // section "Bidirectional Partition Algorithm (Hoare)" + auto result = r; + for (;;) + { + for (;;) + { + if (r.empty) return result; + if (!pred(r.front)) break; + r.popFront(); + result.popFront(); + } + // found the left bound + assert(!r.empty); + for (;;) + { + if (pred(r.back)) break; + r.popBack(); + if (r.empty) return result; + } + // found the right bound, swap & make progress + static if (is(typeof(swap(r.front, r.back)))) + { + swap(r.front, r.back); + } + else + { + auto t1 = moveFront(r), t2 = moveBack(r); + r.front = t2; + r.back = t1; + } + r.popFront(); + result.popFront(); + r.popBack(); + } + } +} + +/// +@safe unittest +{ + import std.algorithm : count, find; // FIXME + import std.conv : text; + + auto Arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto arr = Arr.dup; + static bool even(int a) { return (a & 1) == 0; } + // Partition arr such that even numbers come first + auto r = partition!(even)(arr); + // Now arr is separated in evens and odds. + // Numbers may have become shuffled due to instability + assert(r == arr[5 .. $]); + assert(count!(even)(arr[0 .. 5]) == 5); + assert(find!(even)(r).empty); + + // Can also specify the predicate as a string. + // Use 'a' as the predicate argument name + arr[] = Arr[]; + r = partition!(q{(a & 1) == 0})(arr); + assert(r == arr[5 .. $]); + + // Now for a stable partition: + arr[] = Arr[]; + r = partition!(q{(a & 1) == 0}, SwapStrategy.stable)(arr); + // Now arr is [2 4 6 8 10 1 3 5 7 9], and r points to 1 + assert(arr == [2, 4, 6, 8, 10, 1, 3, 5, 7, 9] && r == arr[5 .. $]); + + // In case the predicate needs to hold its own state, use a delegate: + arr[] = Arr[]; + int x = 3; + // Put stuff greater than 3 on the left + bool fun(int a) { return a > x; } + r = partition!(fun, SwapStrategy.semistable)(arr); + // Now arr is [4 5 6 7 8 9 10 2 3 1] and r points to 2 + assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1] && r == arr[7 .. $]); +} + +@safe unittest +{ + import std.algorithm : rndstuff; // FIXME + static bool even(int a) { return (a & 1) == 0; } + + // test with random data + auto a = rndstuff!int(); + partition!even(a); + assert(isPartitioned!even(a)); + auto b = rndstuff!string(); + partition!`a.length < 5`(b); + assert(isPartitioned!`a.length < 5`(b)); +} + +/** +Returns $(D true) if $(D r) is partitioned according to predicate $(D +pred). + */ +bool isPartitioned(alias pred, Range)(Range r) + if (isForwardRange!(Range)) +{ + for (; !r.empty; r.popFront()) + { + if (unaryFun!(pred)(r.front)) continue; + for (r.popFront(); !r.empty; r.popFront()) + { + if (unaryFun!(pred)(r.front)) return false; + } + break; + } + return true; +} + +/// +@safe unittest +{ + int[] r = [ 1, 3, 5, 7, 8, 2, 4, ]; + assert(isPartitioned!"a & 1"(r)); +} + +// partition3 +/** +Rearranges elements in $(D r) in three adjacent ranges and returns +them. The first and leftmost range only contains elements in $(D r) +less than $(D pivot). The second and middle range only contains +elements in $(D r) that are equal to $(D pivot). Finally, the third +and rightmost range only contains elements in $(D r) that are greater +than $(D pivot). The less-than test is defined by the binary function +$(D less). + +BUGS: stable $(D partition3) has not been implemented yet. + */ +auto partition3(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, E) +(Range r, E pivot) +if (ss == SwapStrategy.unstable && isRandomAccessRange!Range + && hasSwappableElements!Range && hasLength!Range + && is(typeof(binaryFun!less(r.front, pivot)) == bool) + && is(typeof(binaryFun!less(pivot, r.front)) == bool) + && is(typeof(binaryFun!less(r.front, r.front)) == bool)) +{ + // The algorithm is described in "Engineering a sort function" by + // Jon Bentley et al, pp 1257. + + import std.algorithm : swap, swapRanges; // FIXME + import std.algorithm.comparison : min; + import std.typecons : tuple; + + alias lessFun = binaryFun!less; + size_t i, j, k = r.length, l = k; + + bigloop: + for (;;) + { + for (;; ++j) + { + if (j == k) break bigloop; + assert(j < r.length); + if (lessFun(r[j], pivot)) continue; + if (lessFun(pivot, r[j])) break; + swap(r[i++], r[j]); + } + assert(j < k); + for (;;) + { + assert(k > 0); + if (!lessFun(pivot, r[--k])) + { + if (lessFun(r[k], pivot)) break; + swap(r[k], r[--l]); + } + if (j == k) break bigloop; + } + // Here we know r[j] > pivot && r[k] < pivot + swap(r[j++], r[k]); + } + + // Swap the equal ranges from the extremes into the middle + auto strictlyLess = j - i, strictlyGreater = l - k; + auto swapLen = min(i, strictlyLess); + swapRanges(r[0 .. swapLen], r[j - swapLen .. j]); + swapLen = min(r.length - l, strictlyGreater); + swapRanges(r[k .. k + swapLen], r[r.length - swapLen .. r.length]); + return tuple(r[0 .. strictlyLess], + r[strictlyLess .. r.length - strictlyGreater], + r[r.length - strictlyGreater .. r.length]); +} + +/// +@safe unittest +{ + auto a = [ 8, 3, 4, 1, 4, 7, 4 ]; + auto pieces = partition3(a, 4); + assert(pieces[0] == [ 1, 3 ]); + assert(pieces[1] == [ 4, 4, 4 ]); + assert(pieces[2] == [ 8, 7 ]); +} + +@safe unittest +{ + import std.random : uniform; + + auto a = new int[](uniform(0, 100)); + foreach (ref e; a) + { + e = uniform(0, 50); + } + auto pieces = partition3(a, 25); + assert(pieces[0].length + pieces[1].length + pieces[2].length == a.length); + foreach (e; pieces[0]) + { + assert(e < 25); + } + foreach (e; pieces[1]) + { + assert(e == 25); + } + foreach (e; pieces[2]) + { + assert(e > 25); + } +} + +// makeIndex +/** +Computes an index for $(D r) based on the comparison $(D less). The +index is a sorted array of pointers or indices into the original +range. This technique is similar to sorting, but it is more flexible +because (1) it allows "sorting" of immutable collections, (2) allows +binary search even if the original collection does not offer random +access, (3) allows multiple indexes, each on a different predicate, +and (4) may be faster when dealing with large objects. However, using +an index may also be slower under certain circumstances due to the +extra indirection, and is always larger than a sorting-based solution +because it needs space for the index in addition to the original +collection. The complexity is the same as $(D sort)'s. + +The first overload of $(D makeIndex) writes to a range containing +pointers, and the second writes to a range containing offsets. The +first overload requires $(D Range) to be a forward range, and the +latter requires it to be a random-access range. + +$(D makeIndex) overwrites its second argument with the result, but +never reallocates it. + +Returns: The pointer-based version returns a $(D SortedRange) wrapper +over index, of type $(D SortedRange!(RangeIndex, (a, b) => +binaryFun!less(*a, *b))) thus reflecting the ordering of the +index. The index-based version returns $(D void) because the ordering +relation involves not only $(D index) but also $(D r). + +Throws: If the second argument's length is less than that of the range +indexed, an exception is thrown. +*/ +SortedRange!(RangeIndex, (a, b) => binaryFun!less(*a, *b)) +makeIndex( + alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range, + RangeIndex) +(Range r, RangeIndex index) + if (isForwardRange!(Range) && isRandomAccessRange!(RangeIndex) + && is(ElementType!(RangeIndex) : ElementType!(Range)*)) +{ + import std.algorithm : addressOf; // FIXME + import std.exception : enforce; + + // assume collection already ordered + size_t i; + for (; !r.empty; r.popFront(), ++i) + index[i] = addressOf(r.front); + enforce(index.length == i); + // sort the index + sort!((a, b) => binaryFun!less(*a, *b), ss)(index); + return typeof(return)(index); +} + +/// Ditto +void makeIndex( + alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range, + RangeIndex) +(Range r, RangeIndex index) +if (isRandomAccessRange!Range && !isInfinite!Range && + isRandomAccessRange!RangeIndex && !isInfinite!RangeIndex && + isIntegral!(ElementType!RangeIndex)) +{ + import std.exception : enforce; + import std.conv : to; + + alias IndexType = Unqual!(ElementType!RangeIndex); + enforce(r.length == index.length, + "r and index must be same length for makeIndex."); + static if (IndexType.sizeof < size_t.sizeof) + { + enforce(r.length <= IndexType.max, "Cannot create an index with " ~ + "element type " ~ IndexType.stringof ~ " with length " ~ + to!string(r.length) ~ "."); + } + + for (IndexType i = 0; i < r.length; ++i) + { + index[cast(size_t) i] = i; + } + + // sort the index + sort!((a, b) => binaryFun!less(r[cast(size_t) a], r[cast(size_t) b]), ss) + (index); +} + +/// +unittest +{ + immutable(int[]) arr = [ 2, 3, 1, 5, 0 ]; + // index using pointers + auto index1 = new immutable(int)*[arr.length]; + makeIndex!("a < b")(arr, index1); + assert(isSorted!("*a < *b")(index1)); + // index using offsets + auto index2 = new size_t[arr.length]; + makeIndex!("a < b")(arr, index2); + assert(isSorted! + ((size_t a, size_t b){ return arr[a] < arr[b];}) + (index2)); +} + +unittest +{ + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + immutable(int)[] arr = [ 2, 3, 1, 5, 0 ]; + // index using pointers + auto index1 = new immutable(int)*[arr.length]; + alias ImmRange = typeof(arr); + alias ImmIndex = typeof(index1); + static assert(isForwardRange!(ImmRange)); + static assert(isRandomAccessRange!(ImmIndex)); + static assert(!isIntegral!(ElementType!(ImmIndex))); + static assert(is(ElementType!(ImmIndex) : ElementType!(ImmRange)*)); + makeIndex!("a < b")(arr, index1); + assert(isSorted!("*a < *b")(index1)); + + // index using offsets + auto index2 = new long[arr.length]; + makeIndex(arr, index2); + assert(isSorted! + ((long a, long b){ + return arr[cast(size_t) a] < arr[cast(size_t) b]; + })(index2)); + + // index strings using offsets + string[] arr1 = ["I", "have", "no", "chocolate"]; + auto index3 = new byte[arr1.length]; + makeIndex(arr1, index3); + assert(isSorted! + ((byte a, byte b){ return arr1[a] < arr1[b];}) + (index3)); +} + +private template validPredicates(E, less...) +{ + static if (less.length == 0) + enum validPredicates = true; + else static if (less.length == 1 && is(typeof(less[0]) == SwapStrategy)) + enum validPredicates = true; + else + enum validPredicates = + is(typeof((E a, E b){ bool r = binaryFun!(less[0])(a, b); })) + && validPredicates!(E, less[1 .. $]); +} + +/** +$(D void multiSort(Range)(Range r) + if (validPredicates!(ElementType!Range, less));) + +Sorts a range by multiple keys. The call $(D multiSort!("a.id < b.id", +"a.date > b.date")(r)) sorts the range $(D r) by $(D id) ascending, +and sorts elements that have the same $(D id) by $(D date) +descending. Such a call is equivalent to $(D sort!"a.id != b.id ? a.id +< b.id : a.date > b.date"(r)), but $(D multiSort) is faster because it +does fewer comparisons (in addition to being more convenient). + */ +template multiSort(less...) //if (less.length > 1) +{ + void multiSort(Range)(Range r) + if (validPredicates!(ElementType!Range, less)) + { + static if (is(typeof(less[$ - 1]) == SwapStrategy)) + { + enum ss = less[$ - 1]; + alias funs = less[0 .. $ - 1]; + } + else + { + alias ss = SwapStrategy.unstable; + alias funs = less; + } + alias lessFun = binaryFun!(funs[0]); + + static if (funs.length > 1) + { + while (r.length > 1) + { + auto p = getPivot!lessFun(r); + auto t = partition3!(less[0], ss)(r, r[p]); + if (t[0].length <= t[2].length) + { + .multiSort!less(t[0]); + .multiSort!(less[1 .. $])(t[1]); + r = t[2]; + } + else + { + .multiSort!(less[1 .. $])(t[1]); + .multiSort!less(t[2]); + r = t[0]; + } + } + } + else + { + sort!(lessFun, ss)(r); + } + } +} + +/// +@safe unittest +{ + static struct Point { int x, y; } + auto pts1 = [ Point(0, 0), Point(5, 5), Point(0, 1), Point(0, 2) ]; + auto pts2 = [ Point(0, 0), Point(0, 1), Point(0, 2), Point(5, 5) ]; + multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1); + assert(pts1 == pts2); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + + static struct Point { int x, y; } + auto pts1 = [ Point(5, 6), Point(1, 0), Point(5, 7), Point(1, 1), Point(1, 2), Point(0, 1) ]; + auto pts2 = [ Point(0, 1), Point(1, 0), Point(1, 1), Point(1, 2), Point(5, 6), Point(5, 7) ]; + static assert(validPredicates!(Point, "a.x < b.x", "a.y < b.y")); + multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1); + assert(pts1 == pts2); + + auto pts3 = indexed(pts1, iota(pts1.length)); + multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts3); + assert(equal(pts3, pts2)); +} + +@safe unittest //issue 9160 (L-value only comparators) +{ + static struct A + { + int x; + int y; + } + + static bool byX(const ref A lhs, const ref A rhs) + { + return lhs.x < rhs.x; + } + + static bool byY(const ref A lhs, const ref A rhs) + { + return lhs.y < rhs.y; + } + + auto points = [ A(4, 1), A(2, 4)]; + multiSort!(byX, byY)(points); + assert(points[0] == A(2, 4)); + assert(points[1] == A(4, 1)); +} + +private size_t getPivot(alias less, Range)(Range r) +{ + import std.algorithm.mutation : swapAt; + + // This algorithm sorts the first, middle and last elements of r, + // then returns the index of the middle element. In effect, it uses the + // median-of-three heuristic. + + alias pred = binaryFun!(less); + immutable len = r.length; + immutable size_t mid = len / 2; + immutable uint result = ((cast(uint) (pred(r[0], r[mid]))) << 2) | + ((cast(uint) (pred(r[0], r[len - 1]))) << 1) | + (cast(uint) (pred(r[mid], r[len - 1]))); + + switch(result) { + case 0b001: + swapAt(r, 0, len - 1); + swapAt(r, 0, mid); + break; + case 0b110: + swapAt(r, mid, len - 1); + break; + case 0b011: + swapAt(r, 0, mid); + break; + case 0b100: + swapAt(r, mid, len - 1); + swapAt(r, 0, mid); + break; + case 0b000: + swapAt(r, 0, len - 1); + break; + case 0b111: + break; + default: + assert(0); + } + + return mid; +} + +private void optimisticInsertionSort(alias less, Range)(Range r) +{ + import std.algorithm.mutation : swapAt; + + alias pred = binaryFun!(less); + if (r.length < 2) + { + return; + } + + immutable maxJ = r.length - 1; + for (size_t i = r.length - 2; i != size_t.max; --i) + { + size_t j = i; + + static if (hasAssignableElements!Range) + { + auto temp = r[i]; + + for (; j < maxJ && pred(r[j + 1], temp); ++j) + { + r[j] = r[j + 1]; + } + + r[j] = temp; + } + else + { + for (; j < maxJ && pred(r[j + 1], r[j]); ++j) + { + swapAt(r, j, j + 1); + } + } + } +} + +@safe unittest +{ + import std.random : Random, uniform; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + auto rnd = Random(1); + auto a = new int[uniform(100, 200, rnd)]; + foreach (ref e; a) { + e = uniform(-100, 100, rnd); + } + + optimisticInsertionSort!(binaryFun!("a < b"), int[])(a); + assert(isSorted(a)); +} + +// sort +/** +Sorts a random-access range according to the predicate $(D less). Performs +$(BIGOH r.length * log(r.length)) evaluations of $(D less). Stable sorting +requires $(D hasAssignableElements!Range) to be true. + +$(D sort) returns a $(XREF range, SortedRange) over the original range, which +functions that can take advantage of sorted data can then use to know that the +range is sorted and adjust accordingly. The $(XREF range, SortedRange) is a +wrapper around the original range, so both it and the original range are sorted, +but other functions won't know that the original range has been sorted, whereas +they $(I can) know that $(XREF range, SortedRange) has been sorted. + +The predicate is expected to satisfy certain rules in order for $(D sort) to +behave as expected - otherwise, the program may fail on certain inputs (but not +others) when not compiled in release mode, due to the cursory $(D assumeSorted) +check. Specifically, $(D sort) expects $(D less(a,b) && less(b,c)) to imply +$(D less(a,c)) (transitivity), and, conversely, $(D !less(a,b) && !less(b,c)) to +imply $(D !less(a,c)). Note that the default predicate ($(D "a < b")) does not +always satisfy these conditions for floating point types, because the expression +will always be $(D false) when either $(D a) or $(D b) is NaN. + +Returns: The initial range wrapped as a $(D SortedRange) with the predicate +$(D binaryFun!less). + +Algorithms: $(WEB en.wikipedia.org/wiki/Introsort) is used for unstable sorting and +$(WEB en.wikipedia.org/wiki/Timsort, Timsort) is used for stable sorting. +Each algorithm has benefits beyond stability. Introsort is generally faster but +Timsort may achieve greater speeds on data with low entropy or if predicate calls +are expensive. Introsort performs no allocations whereas Timsort will perform one +or more allocations per call. Both algorithms have $(BIGOH n log n) worst-case +time complexity. + +See_Also: + $(XREF range, assumeSorted)$(BR) + $(XREF range, SortedRange)$(BR) + $(XREF algorithm, SwapStrategy)$(BR) + $(XREF functional, binaryFun) +*/ +SortedRange!(Range, less) +sort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range)(Range r) + if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || + hasAssignableElements!Range)) || + (ss != SwapStrategy.unstable && hasAssignableElements!Range)) && + isRandomAccessRange!Range && + hasSlicing!Range && + hasLength!Range) + /+ Unstable sorting uses the quicksort algorithm, which uses swapAt, + which either uses swap(...), requiring swappable elements, or just + swaps using assignment. + Stable sorting uses TimSort, which needs to copy elements into a buffer, + requiring assignable elements. +/ +{ + import std.range : assumeSorted; + alias lessFun = binaryFun!(less); + alias LessRet = typeof(lessFun(r.front, r.front)); // instantiate lessFun + static if (is(LessRet == bool)) + { + static if (ss == SwapStrategy.unstable) + quickSortImpl!(lessFun)(r, r.length); + else //use Tim Sort for semistable & stable + TimSortImpl!(lessFun, Range).sort(r, null); + + enum maxLen = 8; + assert(isSorted!lessFun(r), "Failed to sort range of type " ~ Range.stringof); + } + else + { + static assert(false, "Invalid predicate passed to sort: " ~ less.stringof); + } + return assumeSorted!less(r); +} + +/// +@safe pure nothrow unittest +{ + int[] array = [ 1, 2, 3, 4 ]; + // sort in descending order + sort!("a > b")(array); + assert(array == [ 4, 3, 2, 1 ]); + // sort in ascending order + sort(array); + assert(array == [ 1, 2, 3, 4 ]); + // sort with a delegate + bool myComp(int x, int y) @safe pure nothrow { return x > y; } + sort!(myComp)(array); + assert(array == [ 4, 3, 2, 1 ]); +} +/// +unittest +{ + // Showcase stable sorting + string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; + sort!("toUpper(a) < toUpper(b)", SwapStrategy.stable)(words); + assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); +} + +unittest +{ + import std.algorithm : rndstuff; // FIXME + import std.algorithm : swapRanges; // FIXME + import std.random : Random, unpredictableSeed, uniform; + import std.uni : toUpper; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + // sort using delegate + auto a = new int[100]; + auto rnd = Random(unpredictableSeed); + foreach (ref e; a) { + e = uniform(-100, 100, rnd); + } + + int i = 0; + bool greater2(int a, int b) { return a + i > b + i; } + bool delegate(int, int) greater = &greater2; + sort!(greater)(a); + assert(isSorted!(greater)(a)); + + // sort using string + sort!("a < b")(a); + assert(isSorted!("a < b")(a)); + + // sort using function; all elements equal + foreach (ref e; a) { + e = 5; + } + static bool less(int a, int b) { return a < b; } + sort!(less)(a); + assert(isSorted!(less)(a)); + + string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; + bool lessi(string a, string b) { return toUpper(a) < toUpper(b); } + sort!(lessi, SwapStrategy.stable)(words); + assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); + + // sort using ternary predicate + //sort!("b - a")(a); + //assert(isSorted!(less)(a)); + + a = rndstuff!(int)(); + sort(a); + assert(isSorted(a)); + auto b = rndstuff!(string)(); + sort!("toLower(a) < toLower(b)")(b); + assert(isSorted!("toUpper(a) < toUpper(b)")(b)); + + { + // Issue 10317 + enum E_10317 { a, b } + auto a_10317 = new E_10317[10]; + sort(a_10317); + } + + { + // Issue 7767 + // Unstable sort should complete without an excessive number of predicate calls + // This would suggest it's running in quadratic time + + // Compilation error if predicate is not static, i.e. a nested function + static uint comp; + static bool pred(size_t a, size_t b) + { + ++comp; + return a < b; + } + + size_t[] arr; + arr.length = 1024; + + foreach(k; 0..arr.length) arr[k] = k; + swapRanges(arr[0..$/2], arr[$/2..$]); + + sort!(pred, SwapStrategy.unstable)(arr); + assert(comp < 25_000); + } + + { + import std.algorithm : swap; // FIXME + + bool proxySwapCalled; + struct S + { + int i; + alias i this; + void proxySwap(ref S other) { swap(i, other.i); proxySwapCalled = true; } + @disable void opAssign(S value); + } + + alias R = S[]; + R r = [S(3), S(2), S(1)]; + static assert(hasSwappableElements!R); + static assert(!hasAssignableElements!R); + r.sort(); + assert(proxySwapCalled); + } +} + +private void quickSortImpl(alias less, Range)(Range r, size_t depth) +{ + import std.algorithm : swap; // FIXME + import std.algorithm.mutation : swapAt; + import std.algorithm.comparison : min; + + alias Elem = ElementType!(Range); + enum size_t optimisticInsertionSortGetsBetter = 25; + static assert(optimisticInsertionSortGetsBetter >= 1); + + // partition + while (r.length > optimisticInsertionSortGetsBetter) + { + if (depth == 0) + { + HeapSortImpl!(less, Range).heapSort(r); + return; + } + depth = depth >= depth.max / 2 ? (depth / 3) * 2 : (depth * 2) / 3; + + const pivotIdx = getPivot!(less)(r); + auto pivot = r[pivotIdx]; + + alias pred = binaryFun!(less); + + // partition + swapAt(r, pivotIdx, r.length - 1); + size_t lessI = size_t.max, greaterI = r.length - 1; + + while (true) + { + while (pred(r[++lessI], pivot)) {} + while (greaterI > 0 && pred(pivot, r[--greaterI])) {} + + if (lessI >= greaterI) + { + break; + } + swapAt(r, lessI, greaterI); + } + + swapAt(r, r.length - 1, lessI); + auto right = r[lessI + 1 .. r.length]; + + auto left = r[0 .. min(lessI, greaterI + 1)]; + if (right.length > left.length) + { + swap(left, right); + } + .quickSortImpl!(less, Range)(right, depth); + r = left; + } + // residual sort + static if (optimisticInsertionSortGetsBetter > 1) + { + optimisticInsertionSort!(less, Range)(r); + } +} + +// Bottom-Up Heap-Sort Implementation +private template HeapSortImpl(alias less, Range) +{ + import std.algorithm.mutation : swapAt; + + static assert(isRandomAccessRange!Range); + static assert(hasLength!Range); + static assert(hasSwappableElements!Range || hasAssignableElements!Range); + + alias lessFun = binaryFun!less; + + //template because of @@@12410@@@ + void heapSort()(Range r) + { + // If true, there is nothing to do + if(r.length < 2) return; + + // Build Heap + size_t i = r.length / 2; + while(i > 0) sift(r, --i, r.length); + + // Sort + i = r.length - 1; + while(i > 0) + { + swapAt(r, 0, i); + sift(r, 0, i); + --i; + } + } + + //template because of @@@12410@@@ + void sift()(Range r, size_t parent, immutable size_t end) + { + immutable root = parent; + size_t child = void; + + // Sift down + while(true) + { + child = parent * 2 + 1; + + if(child >= end) break; + + if(child + 1 < end && lessFun(r[child], r[child + 1])) child += 1; + + swapAt(r, parent, child); + parent = child; + } + + child = parent; + + // Sift up + while(child > root) + { + parent = (child - 1) / 2; + if(lessFun(r[parent], r[child])) + { + swapAt(r, parent, child); + child = parent; + } + else break; + } + } +} + +// Tim Sort implementation +private template TimSortImpl(alias pred, R) +{ + import core.bitop : bsr; + import std.array : uninitializedArray; + + static assert(isRandomAccessRange!R); + static assert(hasLength!R); + static assert(hasSlicing!R); + static assert(hasAssignableElements!R); + + alias T = ElementType!R; + + alias less = binaryFun!pred; + bool greater(T a, T b){ return less(b, a); } + bool greaterEqual(T a, T b){ return !less(a, b); } + bool lessEqual(T a, T b){ return !less(b, a); } + + enum minimalMerge = 128; + enum minimalGallop = 7; + enum minimalStorage = 256; + enum stackSize = 40; + + struct Slice{ size_t base, length; } + + // Entry point for tim sort + void sort(R range, T[] temp) + { + import std.algorithm.comparison : min; + + // Do insertion sort on small range + if (range.length <= minimalMerge) + { + binaryInsertionSort(range); + return; + } + + immutable minRun = minRunLength(range.length); + immutable minTemp = min(range.length / 2, minimalStorage); + size_t minGallop = minimalGallop; + Slice[stackSize] stack = void; + size_t stackLen = 0; + + // Allocate temporary memory if not provided by user + if (temp.length < minTemp) + { + if (__ctfe) temp.length = minTemp; + else temp = uninitializedArray!(T[])(minTemp); + } + + for (size_t i = 0; i < range.length; ) + { + // Find length of first run in list + size_t runLen = firstRun(range[i .. range.length]); + + // If run has less than minRun elements, extend using insertion sort + if (runLen < minRun) + { + // Do not run farther than the length of the range + immutable force = range.length - i > minRun ? minRun : range.length - i; + binaryInsertionSort(range[i .. i + force], runLen); + runLen = force; + } + + // Push run onto stack + stack[stackLen++] = Slice(i, runLen); + i += runLen; + + // Collapse stack so that (e1 >= e2 + e3 && e2 >= e3) + // STACK is | ... e1 e2 e3 > + while (stackLen > 1) + { + immutable run3 = stackLen - 1; + immutable run2 = stackLen - 2; + immutable run1 = stackLen - 3; + if (stackLen >= 3 && stack[run1].length <= stack[run2].length + stack[run3].length) + { + immutable at = stack[run1].length <= stack[run3].length + ? run1 : run2; + mergeAt(range, stack[0 .. stackLen], at, minGallop, temp); + --stackLen; + } + else if (stack[run2].length <= stack[run3].length) + { + mergeAt(range, stack[0 .. stackLen], run2, minGallop, temp); + --stackLen; + } + else break; + } + } + + // Force collapse stack until there is only one run left + while (stackLen > 1) + { + immutable run3 = stackLen - 1; + immutable run2 = stackLen - 2; + immutable run1 = stackLen - 3; + immutable at = stackLen >= 3 && stack[run1].length <= stack[run3].length + ? run1 : run2; + mergeAt(range, stack[0 .. stackLen], at, minGallop, temp); + --stackLen; + } + } + + // Calculates optimal value for minRun: + // take first 6 bits of n and add 1 if any lower bits are set + pure size_t minRunLength(size_t n) + { + immutable shift = bsr(n)-5; + auto result = (n>>shift) + !!(n & ~((1<lower; upper--) + range[upper] = moveAt(range, upper-1); + range[lower] = move(item); + } + } + + // Merge two runs in stack (at, at + 1) + void mergeAt(R range, Slice[] stack, immutable size_t at, ref size_t minGallop, ref T[] temp) + in + { + assert(stack.length >= 2); + assert(at == stack.length - 2 || at == stack.length - 3); + } + body + { + immutable base = stack[at].base; + immutable mid = stack[at].length; + immutable len = stack[at + 1].length + mid; + + // Pop run from stack + stack[at] = Slice(base, len); + if (at == stack.length - 3) stack[$ - 2] = stack[$ - 1]; + + // Merge runs (at, at + 1) + return merge(range[base .. base + len], mid, minGallop, temp); + } + + // Merge two runs in a range. Mid is the starting index of the second run. + // minGallop and temp are references; The calling function must receive the updated values. + void merge(R range, size_t mid, ref size_t minGallop, ref T[] temp) + in + { + if (!__ctfe) + { + assert(isSorted!pred(range[0 .. mid])); + assert(isSorted!pred(range[mid .. range.length])); + } + } + body + { + assert(mid < range.length); + + // Reduce range of elements + immutable firstElement = gallopForwardUpper(range[0 .. mid], range[mid]); + immutable lastElement = gallopReverseLower(range[mid .. range.length], range[mid - 1]) + mid; + range = range[firstElement .. lastElement]; + mid -= firstElement; + + if (mid == 0 || mid == range.length) return; + + // Call function which will copy smaller run into temporary memory + if (mid <= range.length / 2) + { + temp = ensureCapacity(mid, temp); + minGallop = mergeLo(range, mid, minGallop, temp); + } + else + { + temp = ensureCapacity(range.length - mid, temp); + minGallop = mergeHi(range, mid, minGallop, temp); + } + } + + // Enlarge size of temporary memory if needed + T[] ensureCapacity(size_t minCapacity, T[] temp) + out(ret) + { + assert(ret.length >= minCapacity); + } + body + { + if (temp.length < minCapacity) + { + size_t newSize = 1<<(bsr(minCapacity)+1); + //Test for overflow + if (newSize < minCapacity) newSize = minCapacity; + + if (__ctfe) temp.length = newSize; + else temp = uninitializedArray!(T[])(newSize); + } + return temp; + } + + // Merge front to back. Returns new value of minGallop. + // temp must be large enough to store range[0 .. mid] + size_t mergeLo(R range, immutable size_t mid, size_t minGallop, T[] temp) + out + { + if (!__ctfe) assert(isSorted!pred(range)); + } + body + { + import std.algorithm : copy; // FIXME + + assert(mid <= range.length); + assert(temp.length >= mid); + + // Copy run into temporary memory + temp = temp[0 .. mid]; + copy(range[0 .. mid], temp); + + // Move first element into place + range[0] = range[mid]; + + size_t i = 1, lef = 0, rig = mid + 1; + size_t count_lef, count_rig; + immutable lef_end = temp.length - 1; + + if (lef < lef_end && rig < range.length) + outer: while(true) + { + count_lef = 0; + count_rig = 0; + + // Linear merge + while ((count_lef | count_rig) < minGallop) + { + if (lessEqual(temp[lef], range[rig])) + { + range[i++] = temp[lef++]; + if(lef >= lef_end) break outer; + ++count_lef; + count_rig = 0; + } + else + { + range[i++] = range[rig++]; + if(rig >= range.length) break outer; + count_lef = 0; + ++count_rig; + } + } + + // Gallop merge + do + { + count_lef = gallopForwardUpper(temp[lef .. $], range[rig]); + foreach (j; 0 .. count_lef) range[i++] = temp[lef++]; + if(lef >= temp.length) break outer; + + count_rig = gallopForwardLower(range[rig .. range.length], temp[lef]); + foreach (j; 0 .. count_rig) range[i++] = range[rig++]; + if (rig >= range.length) while(true) + { + range[i++] = temp[lef++]; + if(lef >= temp.length) break outer; + } + + if (minGallop > 0) --minGallop; + } + while (count_lef >= minimalGallop || count_rig >= minimalGallop); + + minGallop += 2; + } + + // Move remaining elements from right + while (rig < range.length) + range[i++] = range[rig++]; + + // Move remaining elements from left + while (lef < temp.length) + range[i++] = temp[lef++]; + + return minGallop > 0 ? minGallop : 1; + } + + // Merge back to front. Returns new value of minGallop. + // temp must be large enough to store range[mid .. range.length] + size_t mergeHi(R range, immutable size_t mid, size_t minGallop, T[] temp) + out + { + if (!__ctfe) assert(isSorted!pred(range)); + } + body + { + import std.algorithm : copy; // FIXME + + assert(mid <= range.length); + assert(temp.length >= range.length - mid); + + // Copy run into temporary memory + temp = temp[0 .. range.length - mid]; + copy(range[mid .. range.length], temp); + + // Move first element into place + range[range.length - 1] = range[mid - 1]; + + size_t i = range.length - 2, lef = mid - 2, rig = temp.length - 1; + size_t count_lef, count_rig; + + outer: + while(true) + { + count_lef = 0; + count_rig = 0; + + // Linear merge + while((count_lef | count_rig) < minGallop) + { + if(greaterEqual(temp[rig], range[lef])) + { + range[i--] = temp[rig]; + if(rig == 1) + { + // Move remaining elements from left + while(true) + { + range[i--] = range[lef]; + if(lef == 0) break; + --lef; + } + + // Move last element into place + range[i] = temp[0]; + + break outer; + } + --rig; + count_lef = 0; + ++count_rig; + } + else + { + range[i--] = range[lef]; + if(lef == 0) while(true) + { + range[i--] = temp[rig]; + if(rig == 0) break outer; + --rig; + } + --lef; + ++count_lef; + count_rig = 0; + } + } + + // Gallop merge + do + { + count_rig = rig - gallopReverseLower(temp[0 .. rig], range[lef]); + foreach(j; 0 .. count_rig) + { + range[i--] = temp[rig]; + if(rig == 0) break outer; + --rig; + } + + count_lef = lef - gallopReverseUpper(range[0 .. lef], temp[rig]); + foreach(j; 0 .. count_lef) + { + range[i--] = range[lef]; + if(lef == 0) while(true) + { + range[i--] = temp[rig]; + if(rig == 0) break outer; + --rig; + } + --lef; + } + + if(minGallop > 0) --minGallop; + } + while(count_lef >= minimalGallop || count_rig >= minimalGallop); + + minGallop += 2; + } + + return minGallop > 0 ? minGallop : 1; + } + + // false = forward / lower, true = reverse / upper + template gallopSearch(bool forwardReverse, bool lowerUpper) + { + // Gallop search on range according to attributes forwardReverse and lowerUpper + size_t gallopSearch(R)(R range, T value) + out(ret) + { + assert(ret <= range.length); + } + body + { + size_t lower = 0, center = 1, upper = range.length; + alias gap = center; + + static if (forwardReverse) + { + static if (!lowerUpper) alias comp = lessEqual; // reverse lower + static if (lowerUpper) alias comp = less; // reverse upper + + // Gallop Search Reverse + while (gap <= upper) + { + if (comp(value, range[upper - gap])) + { + upper -= gap; + gap *= 2; + } + else + { + lower = upper - gap; + break; + } + } + + // Binary Search Reverse + while (upper != lower) + { + center = lower + (upper - lower) / 2; + if (comp(value, range[center])) upper = center; + else lower = center + 1; + } + } + else + { + static if (!lowerUpper) alias comp = greater; // forward lower + static if (lowerUpper) alias comp = greaterEqual; // forward upper + + // Gallop Search Forward + while (lower + gap < upper) + { + if (comp(value, range[lower + gap])) + { + lower += gap; + gap *= 2; + } + else + { + upper = lower + gap; + break; + } + } + + // Binary Search Forward + while (lower != upper) + { + center = lower + (upper - lower) / 2; + if (comp(value, range[center])) lower = center + 1; + else upper = center; + } + } + + return lower; + } + } + + alias gallopForwardLower = gallopSearch!(false, false); + alias gallopForwardUpper = gallopSearch!(false, true); + alias gallopReverseLower = gallopSearch!( true, false); + alias gallopReverseUpper = gallopSearch!( true, true); +} + +unittest +{ + import std.random : Random, uniform, randomShuffle; + + // Element type with two fields + static struct E + { + size_t value, index; + } + + // Generates data especially for testing sorting with Timsort + static E[] genSampleData(uint seed) + { + import std.algorithm : swap, swapRanges; // FIXME + + auto rnd = Random(seed); + + E[] arr; + arr.length = 64 * 64; + + // We want duplicate values for testing stability + foreach(i, ref v; arr) v.value = i / 64; + + // Swap ranges at random middle point (test large merge operation) + immutable mid = uniform(arr.length / 4, arr.length / 4 * 3, rnd); + swapRanges(arr[0 .. mid], arr[mid .. $]); + + // Shuffle last 1/8 of the array (test insertion sort and linear merge) + randomShuffle(arr[$ / 8 * 7 .. $], rnd); + + // Swap few random elements (test galloping mode) + foreach(i; 0 .. arr.length / 64) + { + immutable a = uniform(0, arr.length, rnd), b = uniform(0, arr.length, rnd); + swap(arr[a], arr[b]); + } + + // Now that our test array is prepped, store original index value + // This will allow us to confirm the array was sorted stably + foreach(i, ref v; arr) v.index = i; + + return arr; + } + + // Tests the Timsort function for correctness and stability + static bool testSort(uint seed) + { + auto arr = genSampleData(seed); + + // Now sort the array! + static bool comp(E a, E b) + { + return a.value < b.value; + } + + sort!(comp, SwapStrategy.stable)(arr); + + // Test that the array was sorted correctly + assert(isSorted!comp(arr)); + + // Test that the array was sorted stably + foreach(i; 0 .. arr.length - 1) + { + if(arr[i].value == arr[i + 1].value) assert(arr[i].index < arr[i + 1].index); + } + + return true; + } + + enum seed = 310614065; + testSort(seed); + + //@@BUG: Timsort fails with CTFE as of DMD 2.060 + // enum result = testSort(seed); +} + +unittest +{//bugzilla 4584 + assert(isSorted!"a < b"(sort!("a < b", SwapStrategy.stable)( + [83, 42, 85, 86, 87, 22, 89, 30, 91, 46, 93, 94, 95, 6, + 97, 14, 33, 10, 101, 102, 103, 26, 105, 106, 107, 6] + ))); + +} + +unittest +{ + //test stable sort + zip + import std.range; + auto x = [10, 50, 60, 60, 20]; + dchar[] y = "abcde"d.dup; + + sort!("a[0] < b[0]", SwapStrategy.stable)(zip(x, y)); + assert(x == [10, 20, 50, 60, 60]); + assert(y == "aebcd"d); +} + +// schwartzSort +/** +Sorts a range using an algorithm akin to the $(WEB +wikipedia.org/wiki/Schwartzian_transform, Schwartzian transform), also +known as the decorate-sort-undecorate pattern in Python and Lisp. (Not +to be confused with $(WEB youtube.com/watch?v=UHw6KXbvazs, the other +Schwartz).) This function is helpful when the sort comparison includes +an expensive computation. The complexity is the same as that of the +corresponding $(D sort), but $(D schwartzSort) evaluates $(D +transform) only $(D r.length) times (less than half when compared to +regular sorting). The usage can be best illustrated with an example. + +Examples: +---- +uint hashFun(string) { ... expensive computation ... } +string[] array = ...; +// Sort strings by hash, slow +sort!((a, b) => hashFun(a) < hashFun(b))(array); +// Sort strings by hash, fast (only computes arr.length hashes): +schwartzSort!(hashFun, "a < b")(array); +---- + +The $(D schwartzSort) function might require less temporary data and +be faster than the Perl idiom or the decorate-sort-undecorate idiom +present in Python and Lisp. This is because sorting is done in-place +and only minimal extra data (one array of transformed elements) is +created. + +To check whether an array was sorted and benefit of the speedup of +Schwartz sorting, a function $(D schwartzIsSorted) is not provided +because the effect can be achieved by calling $(D +isSorted!less(map!transform(r))). + +Returns: The initial range wrapped as a $(D SortedRange) with the +predicate $(D (a, b) => binaryFun!less(transform(a), +transform(b))). + */ +SortedRange!(R, ((a, b) => binaryFun!less(unaryFun!transform(a), + unaryFun!transform(b)))) +schwartzSort(alias transform, alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, R)(R r) + if (isRandomAccessRange!R && hasLength!R) +{ + import core.stdc.stdlib : malloc, free; + import std.conv : emplace; + import std.string : representation; + import std.range : zip, SortedRange; + + alias T = typeof(unaryFun!transform(r.front)); + auto xform1 = (cast(T*) malloc(r.length * T.sizeof))[0 .. r.length]; + size_t length; + scope(exit) + { + static if (hasElaborateDestructor!T) + { + foreach (i; 0 .. length) collectException(destroy(xform1[i])); + } + free(xform1.ptr); + } + for (; length != r.length; ++length) + { + emplace(xform1.ptr + length, unaryFun!transform(r[length])); + } + // Make sure we use ubyte[] and ushort[], not char[] and wchar[] + // for the intermediate array, lest zip gets confused. + static if (isNarrowString!(typeof(xform1))) + { + auto xform = xform1.representation(); + } + else + { + alias xform = xform1; + } + zip(xform, r).sort!((a, b) => binaryFun!less(a[0], b[0]), ss)(); + return typeof(return)(r); +} + +unittest +{ + // issue 4909 + import std.typecons : Tuple; + Tuple!(char)[] chars; + schwartzSort!"a[0]"(chars); +} + +unittest +{ + // issue 5924 + import std.typecons : Tuple; + Tuple!(char)[] chars; + schwartzSort!((Tuple!(char) c){ return c[0]; })(chars); +} + +unittest +{ + import std.algorithm.iteration : map; + import std.math : log2; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + static double entropy(double[] probs) { + double result = 0; + foreach (p; probs) { + if (!p) continue; + //enforce(p > 0 && p <= 1, "Wrong probability passed to entropy"); + result -= p * log2(p); + } + return result; + } + + auto lowEnt = ([ 1.0, 0, 0 ]).dup, + midEnt = ([ 0.1, 0.1, 0.8 ]).dup, + highEnt = ([ 0.31, 0.29, 0.4 ]).dup; + auto arr = new double[][3]; + arr[0] = midEnt; + arr[1] = lowEnt; + arr[2] = highEnt; + + schwartzSort!(entropy, q{a > b})(arr); + assert(arr[0] == highEnt); + assert(arr[1] == midEnt); + assert(arr[2] == lowEnt); + assert(isSorted!("a > b")(map!(entropy)(arr))); +} + +unittest +{ + import std.algorithm.iteration : map; + import std.math : log2; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + static double entropy(double[] probs) { + double result = 0; + foreach (p; probs) { + if (!p) continue; + //enforce(p > 0 && p <= 1, "Wrong probability passed to entropy"); + result -= p * log2(p); + } + return result; + } + + auto lowEnt = ([ 1.0, 0, 0 ]).dup, + midEnt = ([ 0.1, 0.1, 0.8 ]).dup, + highEnt = ([ 0.31, 0.29, 0.4 ]).dup; + auto arr = new double[][3]; + arr[0] = midEnt; + arr[1] = lowEnt; + arr[2] = highEnt; + + schwartzSort!(entropy, q{a < b})(arr); + assert(arr[0] == lowEnt); + assert(arr[1] == midEnt); + assert(arr[2] == highEnt); + assert(isSorted!("a < b")(map!(entropy)(arr))); +} + +// partialSort +/** +Reorders the random-access range $(D r) such that the range $(D r[0 +.. mid]) is the same as if the entire $(D r) were sorted, and leaves +the range $(D r[mid .. r.length]) in no particular order. Performs +$(BIGOH r.length * log(mid)) evaluations of $(D pred). The +implementation simply calls $(D topN!(less, ss)(r, n)) and then $(D +sort!(less, ss)(r[0 .. n])). +*/ +void partialSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range)(Range r, size_t n) + if (isRandomAccessRange!(Range) && hasLength!(Range) && hasSlicing!(Range)) +{ + topN!(less, ss)(r, n); + sort!(less, ss)(r[0 .. n]); +} + +/// +@safe unittest +{ + int[] a = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]; + partialSort(a, 5); + assert(a[0 .. 5] == [ 0, 1, 2, 3, 4 ]); +} + +// topN +/** +Reorders the range $(D r) using $(D swap) such that $(D r[nth]) refers +to the element that would fall there if the range were fully +sorted. In addition, it also partitions $(D r) such that all elements +$(D e1) from $(D r[0]) to $(D r[nth]) satisfy $(D !less(r[nth], e1)), +and all elements $(D e2) from $(D r[nth]) to $(D r[r.length]) satisfy +$(D !less(e2, r[nth])). Effectively, it finds the nth smallest +(according to $(D less)) elements in $(D r). Performs an expected +$(BIGOH r.length) (if unstable) or $(BIGOH r.length * log(r.length)) +(if stable) evaluations of $(D less) and $(D swap). + +If $(D n >= r.length), the algorithm has no effect. + +See_Also: + $(LREF topNIndex), + $(WEB sgi.com/tech/stl/nth_element.html, STL's nth_element) + +BUGS: + +Stable topN has not been implemented yet. +*/ +void topN(alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range)(Range r, size_t nth) + if (isRandomAccessRange!(Range) && hasLength!Range) +{ + import std.algorithm : swap; // FIXME + import std.random : uniform; + + static assert(ss == SwapStrategy.unstable, + "Stable topN not yet implemented"); + while (r.length > nth) + { + auto pivot = uniform(0, r.length); + swap(r[pivot], r.back); + assert(!binaryFun!(less)(r.back, r.back)); + auto right = partition!((a) => binaryFun!less(a, r.back), ss)(r); + assert(right.length >= 1); + swap(right.front, r.back); + pivot = r.length - right.length; + if (pivot == nth) + { + return; + } + if (pivot < nth) + { + ++pivot; + r = r[pivot .. $]; + nth -= pivot; + } + else + { + assert(pivot < r.length); + r = r[0 .. pivot]; + } + } +} + +/// +@safe unittest +{ + int[] v = [ 25, 7, 9, 2, 0, 5, 21 ]; + auto n = 4; + topN!"a < b"(v, n); + assert(v[n] == 9); +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.algorithm.iteration : reduce; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + //scope(failure) writeln(stderr, "Failure testing algorithm"); + //auto v = ([ 25, 7, 9, 2, 0, 5, 21 ]).dup; + int[] v = [ 7, 6, 5, 4, 3, 2, 1, 0 ]; + ptrdiff_t n = 3; + topN!("a < b")(v, n); + assert(reduce!max(v[0 .. n]) <= v[n]); + assert(reduce!min(v[n + 1 .. $]) >= v[n]); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = 3; + topN(v, n); + assert(reduce!max(v[0 .. n]) <= v[n]); + assert(reduce!min(v[n + 1 .. $]) >= v[n]); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = 1; + topN(v, n); + assert(reduce!max(v[0 .. n]) <= v[n]); + assert(reduce!min(v[n + 1 .. $]) >= v[n]); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = v.length - 1; + topN(v, n); + assert(v[n] == 7); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = 0; + topN(v, n); + assert(v[n] == 1); + + double[][] v1 = [[-10, -5], [-10, -3], [-10, -5], [-10, -4], + [-10, -5], [-9, -5], [-9, -3], [-9, -5],]; + + // double[][] v1 = [ [-10, -5], [-10, -4], [-9, -5], [-9, -5], + // [-10, -5], [-10, -3], [-10, -5], [-9, -3],]; + double[]*[] idx = [ &v1[0], &v1[1], &v1[2], &v1[3], &v1[4], &v1[5], &v1[6], + &v1[7], ]; + + auto mid = v1.length / 2; + topN!((a, b){ return (*a)[1] < (*b)[1]; })(idx, mid); + foreach (e; idx[0 .. mid]) assert((*e)[1] <= (*idx[mid])[1]); + foreach (e; idx[mid .. $]) assert((*e)[1] >= (*idx[mid])[1]); +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.algorithm.iteration : reduce; + import std.random : uniform; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + int[] a = new int[uniform(1, 10000)]; + foreach (ref e; a) e = uniform(-1000, 1000); + auto k = uniform(0, a.length); + topN(a, k); + if (k > 0) + { + auto left = reduce!max(a[0 .. k]); + assert(left <= a[k]); + } + if (k + 1 < a.length) + { + auto right = reduce!min(a[k + 1 .. $]); + assert(right >= a[k]); + } +} + +/** +Stores the smallest elements of the two ranges in the left-hand range. + */ +void topN(alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range1, Range2)(Range1 r1, Range2 r2) + if (isRandomAccessRange!(Range1) && hasLength!Range1 && + isInputRange!Range2 && is(ElementType!Range1 == ElementType!Range2)) +{ + import std.container : BinaryHeap; + + static assert(ss == SwapStrategy.unstable, + "Stable topN not yet implemented"); + auto heap = BinaryHeap!Range1(r1); + for (; !r2.empty; r2.popFront()) + { + heap.conditionalInsert(r2.front); + } +} + +/// +unittest +{ + int[] a = [ 5, 7, 2, 6, 7 ]; + int[] b = [ 2, 1, 5, 6, 7, 3, 0 ]; + topN(a, b); + sort(a); + assert(a == [0, 1, 2, 2, 3]); +} + +/** +Copies the top $(D n) elements of the input range $(D source) into the +random-access range $(D target), where $(D n = +target.length). Elements of $(D source) are not touched. If $(D +sorted) is $(D true), the target is sorted. Otherwise, the target +respects the $(WEB en.wikipedia.org/wiki/Binary_heap, heap property). + */ +TRange topNCopy(alias less = "a < b", SRange, TRange) + (SRange source, TRange target, SortOutput sorted = SortOutput.no) + if (isInputRange!(SRange) && isRandomAccessRange!(TRange) + && hasLength!(TRange) && hasSlicing!(TRange)) +{ + import std.container : BinaryHeap; + + if (target.empty) return target; + auto heap = BinaryHeap!(TRange, less)(target, 0); + foreach (e; source) heap.conditionalInsert(e); + auto result = target[0 .. heap.length]; + if (sorted == SortOutput.yes) + { + while (!heap.empty) heap.removeFront(); + } + return result; +} + +/// +unittest +{ + int[] a = [ 10, 16, 2, 3, 1, 5, 0 ]; + int[] b = new int[3]; + topNCopy(a, b, SortOutput.yes); + assert(b == [ 0, 1, 2 ]); +} + +unittest +{ + import std.random : Random, unpredictableSeed, uniform, randomShuffle; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + auto r = Random(unpredictableSeed); + ptrdiff_t[] a = new ptrdiff_t[uniform(1, 1000, r)]; + foreach (i, ref e; a) e = i; + randomShuffle(a, r); + auto n = uniform(0, a.length, r); + ptrdiff_t[] b = new ptrdiff_t[n]; + topNCopy!(binaryFun!("a < b"))(a, b, SortOutput.yes); + assert(isSorted!(binaryFun!("a < b"))(b)); +} + +/** +Given a range of elements, constructs an index of its top $(I n) elements +(i.e., the first $(I n) elements if the range were sorted). + +Similar to $(LREF topN), except that the range is not modified. + +Params: + less = A binary predicate that defines the ordering of range elements. + Defaults to $(D a < b). + ss = $(RED (Not implemented yet.)) Specify the swapping strategy. + r = A $(XREF2 range, isRandomAccessRange, random-access range) of elements + to make an index for. + index = A $(XREF2 range, isRandomAccessRange, random-access range) with + assignable elements to build the index in. The length of this range + determines how many top elements to index in $(D r). + + This index range can either have integral elements, in which case the + constructed index will consist of zero-based numerical indices into + $(D r); or it can have pointers to the element type of $(D r), in which + case the constructed index will be pointers to the top elements in + $(D r). + sorted = Determines whether to sort the index by the elements they refer + to. + +See_also: $(LREF topN), $(LREF topNCopy). + +BUGS: +The swapping strategy parameter is not implemented yet; currently it is +ignored. +*/ +void topNIndex(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range, RangeIndex) + (Range r, RangeIndex index, SortOutput sorted = SortOutput.no) + if (isRandomAccessRange!Range && + isRandomAccessRange!RangeIndex && + hasAssignableElements!RangeIndex && + isIntegral!(ElementType!(RangeIndex))) +{ + static assert(ss == SwapStrategy.unstable, + "Stable swap strategy not implemented yet."); + + import std.container : BinaryHeap; + import std.exception : enforce; + + if (index.empty) return; + enforce(ElementType!(RangeIndex).max >= index.length, + "Index type too small"); + bool indirectLess(ElementType!(RangeIndex) a, ElementType!(RangeIndex) b) + { + return binaryFun!(less)(r[a], r[b]); + } + auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); + foreach (i; 0 .. r.length) + { + heap.conditionalInsert(cast(ElementType!RangeIndex) i); + } + if (sorted == SortOutput.yes) + { + while (!heap.empty) heap.removeFront(); + } +} + +/// ditto +void topNIndex(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range, RangeIndex) + (Range r, RangeIndex index, SortOutput sorted = SortOutput.no) + if (isRandomAccessRange!Range && + isRandomAccessRange!RangeIndex && + hasAssignableElements!RangeIndex && + is(ElementType!(RangeIndex) == ElementType!(Range)*)) +{ + static assert(ss == SwapStrategy.unstable, + "Stable swap strategy not implemented yet."); + + import std.container : BinaryHeap; + + if (index.empty) return; + static bool indirectLess(const ElementType!(RangeIndex) a, + const ElementType!(RangeIndex) b) + { + return binaryFun!less(*a, *b); + } + auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); + foreach (i; 0 .. r.length) + { + heap.conditionalInsert(&r[i]); + } + if (sorted == SortOutput.yes) + { + while (!heap.empty) heap.removeFront(); + } +} + +/// +unittest +{ + // Construct index to top 3 elements using numerical indices: + int[] a = [ 10, 2, 7, 5, 8, 1 ]; + int[] index = new int[3]; + topNIndex(a, index, SortOutput.yes); + assert(index == [5, 1, 3]); // because a[5]==1, a[1]==2, a[3]==5 + + // Construct index to top 3 elements using pointer indices: + int*[] ptrIndex = new int*[3]; + topNIndex(a, ptrIndex, SortOutput.yes); + assert(ptrIndex == [ &a[5], &a[1], &a[3] ]); +} + +unittest +{ + import std.conv : text; + + debug(std_algorithm) scope(success) + writeln("unittest @", __FILE__, ":", __LINE__, " done."); + + { + int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; + int*[] b = new int*[5]; + topNIndex!("a > b")(a, b, SortOutput.yes); + //foreach (e; b) writeln(*e); + assert(b == [ &a[0], &a[2], &a[1], &a[6], &a[5]]); + } + { + int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; + auto b = new ubyte[5]; + topNIndex!("a > b")(a, b, SortOutput.yes); + //foreach (e; b) writeln(e, ":", a[e]); + assert(b == [ cast(ubyte) 0, cast(ubyte)2, cast(ubyte)1, cast(ubyte)6, cast(ubyte)5], text(b)); + } +} + +// nextPermutation +/** + * Permutes $(D range) in-place to the next lexicographically greater + * permutation. + * + * The predicate $(D less) defines the lexicographical ordering to be used on + * the range. + * + * If the range is currently the lexicographically greatest permutation, it is + * permuted back to the least permutation and false is returned. Otherwise, + * true is returned. One can thus generate all permutations of a range by + * sorting it according to $(D less), which produces the lexicographically + * least permutation, and then calling nextPermutation until it returns false. + * This is guaranteed to generate all distinct permutations of the range + * exactly once. If there are $(I N) elements in the range and all of them are + * unique, then $(I N)! permutations will be generated. Otherwise, if there are + * some duplicated elements, fewer permutations will be produced. +---- +// Enumerate all permutations +int[] a = [1,2,3,4,5]; +do +{ + // use the current permutation and + // proceed to the next permutation of the array. +} while (nextPermutation(a)); +---- + * Returns: false if the range was lexicographically the greatest, in which + * case the range is reversed back to the lexicographically smallest + * permutation; otherwise returns true. + */ +bool nextPermutation(alias less="a < b", BidirectionalRange) + (BidirectionalRange range) + if (isBidirectionalRange!BidirectionalRange && + hasSwappableElements!BidirectionalRange) +{ + import std.algorithm : find, reverse, swap; // FIXME + import std.range : retro, takeExactly; + // Ranges of 0 or 1 element have no distinct permutations. + if (range.empty) return false; + + auto i = retro(range); + auto last = i.save; + + // Find last occurring increasing pair of elements + size_t n = 1; + for (i.popFront(); !i.empty; i.popFront(), last.popFront(), n++) + { + if (binaryFun!less(i.front, last.front)) + break; + } + + if (i.empty) { + // Entire range is decreasing: it's lexicographically the greatest. So + // wrap it around. + range.reverse(); + return false; + } + + // Find last element greater than i.front. + auto j = find!((a) => binaryFun!less(i.front, a))( + takeExactly(retro(range), n)); + + assert(!j.empty); // shouldn't happen since i.front < last.front + swap(i.front, j.front); + reverse(takeExactly(retro(range), n)); + + return true; +} + +/// +@safe unittest +{ + // Step through all permutations of a sorted array in lexicographic order + int[] a = [1,2,3]; + assert(nextPermutation(a) == true); + assert(a == [1,3,2]); + assert(nextPermutation(a) == true); + assert(a == [2,1,3]); + assert(nextPermutation(a) == true); + assert(a == [2,3,1]); + assert(nextPermutation(a) == true); + assert(a == [3,1,2]); + assert(nextPermutation(a) == true); + assert(a == [3,2,1]); + assert(nextPermutation(a) == false); + assert(a == [1,2,3]); +} + +/// +@safe unittest +{ + // Step through permutations of an array containing duplicate elements: + int[] a = [1,1,2]; + assert(nextPermutation(a) == true); + assert(a == [1,2,1]); + assert(nextPermutation(a) == true); + assert(a == [2,1,1]); + assert(nextPermutation(a) == false); + assert(a == [1,1,2]); +} + +@safe unittest +{ + // Boundary cases: arrays of 0 or 1 element. + int[] a1 = []; + assert(!nextPermutation(a1)); + assert(a1 == []); + + int[] a2 = [1]; + assert(!nextPermutation(a2)); + assert(a2 == [1]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto a1 = [1, 2, 3, 4]; + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 2, 4, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 3, 2, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 3, 4, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 4, 2, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 4, 3, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 1, 3, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 1, 4, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 3, 1, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 3, 4, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 4, 1, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 4, 3, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 1, 2, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 1, 4, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 2, 1, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 2, 4, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 4, 1, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 4, 2, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 1, 2, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 1, 3, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 2, 1, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 2, 3, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 3, 1, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 3, 2, 1])); + + assert(!nextPermutation(a1)); + assert(equal(a1, [1, 2, 3, 4])); +} + +@safe unittest +{ + // Test with non-default sorting order + int[] a = [3,2,1]; + assert(nextPermutation!"a > b"(a) == true); + assert(a == [3,1,2]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [2,3,1]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [2,1,3]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [1,3,2]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [1,2,3]); + assert(nextPermutation!"a > b"(a) == false); + assert(a == [3,2,1]); +} + +// Issue 13594 +@safe unittest +{ + int[3] a = [1,2,3]; + assert(nextPermutation(a[])); + assert(a == [1,3,2]); +} + +// nextEvenPermutation +/** + * Permutes $(D range) in-place to the next lexicographically greater $(I even) + * permutation. + * + * The predicate $(D less) defines the lexicographical ordering to be used on + * the range. + * + * An even permutation is one which is produced by swapping an even number of + * pairs of elements in the original range. The set of $(I even) permutations + * is distinct from the set of $(I all) permutations only when there are no + * duplicate elements in the range. If the range has $(I N) unique elements, + * then there are exactly $(I N)!/2 even permutations. + * + * If the range is already the lexicographically greatest even permutation, it + * is permuted back to the least even permutation and false is returned. + * Otherwise, true is returned, and the range is modified in-place to be the + * lexicographically next even permutation. + * + * One can thus generate the even permutations of a range with unique elements + * by starting with the lexicographically smallest permutation, and repeatedly + * calling nextEvenPermutation until it returns false. +---- +// Enumerate even permutations +int[] a = [1,2,3,4,5]; +do +{ + // use the current permutation and + // proceed to the next even permutation of the array. +} while (nextEvenPermutation(a)); +---- + * One can also generate the $(I odd) permutations of a range by noting that + * permutations obey the rule that even + even = even, and odd + even = odd. + * Thus, by swapping the last two elements of a lexicographically least range, + * it is turned into the first odd permutation. Then calling + * nextEvenPermutation on this first odd permutation will generate the next + * even permutation relative to this odd permutation, which is actually the + * next odd permutation of the original range. Thus, by repeatedly calling + * nextEvenPermutation until it returns false, one enumerates the odd + * permutations of the original range. +---- +// Enumerate odd permutations +int[] a = [1,2,3,4,5]; +swap(a[$-2], a[$-1]); // a is now the first odd permutation of [1,2,3,4,5] +do +{ + // use the current permutation and + // proceed to the next odd permutation of the original array + // (which is an even permutation of the first odd permutation). +} while (nextEvenPermutation(a)); +---- + * + * Warning: Since even permutations are only distinct from all permutations + * when the range elements are unique, this function assumes that there are no + * duplicate elements under the specified ordering. If this is not _true, some + * permutations may fail to be generated. When the range has non-unique + * elements, you should use $(MYREF nextPermutation) instead. + * + * Returns: false if the range was lexicographically the greatest, in which + * case the range is reversed back to the lexicographically smallest + * permutation; otherwise returns true. + */ +bool nextEvenPermutation(alias less="a < b", BidirectionalRange) + (BidirectionalRange range) + if (isBidirectionalRange!BidirectionalRange && + hasSwappableElements!BidirectionalRange) +{ + import std.algorithm : find, reverse, swap; // FIXME + import std.range : retro, takeExactly; + // Ranges of 0 or 1 element have no distinct permutations. + if (range.empty) return false; + + bool oddParity = false; + bool ret = true; + do + { + auto i = retro(range); + auto last = i.save; + + // Find last occurring increasing pair of elements + size_t n = 1; + for (i.popFront(); !i.empty; + i.popFront(), last.popFront(), n++) + { + if (binaryFun!less(i.front, last.front)) + break; + } + + if (!i.empty) + { + // Find last element greater than i.front. + auto j = find!((a) => binaryFun!less(i.front, a))( + takeExactly(retro(range), n)); + + // shouldn't happen since i.front < last.front + assert(!j.empty); + + swap(i.front, j.front); + oddParity = !oddParity; + } + else + { + // Entire range is decreasing: it's lexicographically + // the greatest. + ret = false; + } + + reverse(takeExactly(retro(range), n)); + if ((n / 2) % 2 == 1) + oddParity = !oddParity; + } while(oddParity); + + return ret; +} + +/// +@safe unittest +{ + // Step through even permutations of a sorted array in lexicographic order + int[] a = [1,2,3]; + assert(nextEvenPermutation(a) == true); + assert(a == [2,3,1]); + assert(nextEvenPermutation(a) == true); + assert(a == [3,1,2]); + assert(nextEvenPermutation(a) == false); + assert(a == [1,2,3]); +} + +@safe unittest +{ + auto a3 = [ 1, 2, 3, 4 ]; + int count = 1; + while (nextEvenPermutation(a3)) count++; + assert(count == 12); +} + +@safe unittest +{ + // Test with non-default sorting order + auto a = [ 3, 2, 1 ]; + + assert(nextEvenPermutation!"a > b"(a) == true); + assert(a == [ 2, 1, 3 ]); + assert(nextEvenPermutation!"a > b"(a) == true); + assert(a == [ 1, 3, 2 ]); + assert(nextEvenPermutation!"a > b"(a) == false); + assert(a == [ 3, 2, 1 ]); +} + +@safe unittest +{ + // Test various cases of rollover + auto a = [ 3, 1, 2 ]; + assert(nextEvenPermutation(a) == false); + assert(a == [ 1, 2, 3 ]); + + auto b = [ 3, 2, 1 ]; + assert(nextEvenPermutation(b) == false); + assert(b == [ 1, 3, 2 ]); +} + +@safe unittest +{ + // Issue 13594 + int[3] a = [1,2,3]; + assert(nextEvenPermutation(a[])); + assert(a == [2,3,1]); +} + +/** +Even permutations are useful for generating coordinates of certain geometric +shapes. Here's a non-trivial example: +*/ +@safe unittest +{ + import std.math : sqrt; + + // Print the 60 vertices of a uniform truncated icosahedron (soccer ball) + enum real Phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio + real[][] seeds = [ + [0.0, 1.0, 3.0*Phi], + [1.0, 2.0+Phi, 2.0*Phi], + [Phi, 2.0, Phi^^3] + ]; + size_t n; + foreach (seed; seeds) + { + // Loop over even permutations of each seed + do + { + // Loop over all sign changes of each permutation + size_t i; + do + { + // Generate all possible sign changes + for (i=0; i < seed.length; i++) + { + if (seed[i] != 0.0) + { + seed[i] = -seed[i]; + if (seed[i] < 0.0) + break; + } + } + n++; + } while (i < seed.length); + } while (nextEvenPermutation(seed)); + } + assert(n == 60); +} + diff --git a/std/array.d b/std/array.d index e3781d93b53..194785f24c0 100644 --- a/std/array.d +++ b/std/array.d @@ -1,6 +1,59 @@ // Written in the D programming language. /** -Functions and types that manipulate built-in arrays. +Functions and types that manipulate built-in arrays and associative arrays. + +This module provides all kinds of functions to create, manipulate or convert arrays: + +$(BOOKTABLE , + $(TR $(TD $(D $(LREF _array))) + $(TD Returns a copy of the input in a newly allocated dynamic _array. + )) + $(TR $(TD $(D $(LREF appender))) + $(TD Returns a new Appender initialized with a given _array. + )) + $(TR $(TD $(D $(LREF assocArray))) + $(TD Returns a newly allocated associative _array from a range of key/value tuples. + )) + $(TR $(TD $(D $(LREF byPair))) + $(TD Construct a range iterating over an associative _array by key/value tuples. + )) + $(TR $(TD $(D $(LREF insertInPlace))) + $(TD Inserts into an existing _array at a given position. + )) + $(TR $(TD $(D $(LREF join))) + $(TD Concatenates a range of ranges into one _array. + )) + $(TR $(TD $(D $(LREF minimallyInitializedArray))) + $(TD Returns a new _array of type $(D T). + )) + $(TR $(TD $(D $(LREF replace))) + $(TD Returns a new _array with all occurrences of a certain subrange replaced. + )) + $(TR $(TD $(D $(LREF replaceFirst))) + $(TD Returns a new _array with the first occurrence of a certain subrange replaced. + )) + $(TR $(TD $(D $(LREF replaceInPlace))) + $(TD Replaces all occurrences of a certain subrange and puts the result into a given _array. + )) + $(TR $(TD $(D $(LREF replaceInto))) + $(TD Replaces all occurrences of a certain subrange and puts the result into an output range. + )) + $(TR $(TD $(D $(LREF replaceLast))) + $(TD Returns a new _array with the last occurrence of a certain subrange replaced. + )) + $(TR $(TD $(D $(LREF replaceSlice))) + $(TD Returns a new _array with a given slice replaced. + )) + $(TR $(TD $(D $(LREF replicate))) + $(TD Creates a new _array out of several copies of an input _array or range. + )) + $(TR $(TD $(D $(LREF split))) + $(TD Eagerly split a range or string into an _array. + )) + $(TR $(TD $(D $(LREF uninitializedArray))) + $(TD Returns a new _array of type $(D T) without initializing its elements. + )) +) Copyright: Copyright Andrei Alexandrescu 2008- and Jonathan M Davis 2011-. @@ -12,13 +65,16 @@ Source: $(PHOBOSSRC std/_array.d) */ module std.array; -import core.memory, core.bitop; -import std.algorithm, std.ascii, std.conv, std.exception, std.functional, - std.range, std.string, std.traits, std.typecons, std.typetuple, std.utf; -version(unittest) import core.exception, std.stdio; +import std.traits; +import std.typetuple; +import std.functional; +static import std.algorithm; // FIXME, remove with alias of splitter + +import std.range.primitives; +public import std.range.primitives : save, empty, popFront, popBack, front, back; /** -Returns a newly-allocated dynamic array consisting of a copy of the +Returns a newly allocated dynamic array consisting of a copy of the input range, static array, dynamic array, or class or struct with an $(D opApply) function $(D r). Note that narrow strings are handled as a special case in an overload. @@ -38,6 +94,7 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) alias E = ForeachType!Range; static if (hasLength!Range) { + import std.conv : emplaceRef; if(r.length == 0) return null; static auto trustedAllocateArray(size_t n) @trusted nothrow @@ -74,6 +131,7 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) @safe pure nothrow unittest { + import std.algorithm : equal; struct Foo { int a; @@ -84,6 +142,7 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) @system unittest { + import std.algorithm : equal; struct Foo { int a; @@ -110,6 +169,7 @@ unittest unittest { + import std.range; static struct S{int* p;} auto a = array(immutable(S).init.repeat(5)); } @@ -122,12 +182,15 @@ the input. */ ElementType!String[] array(String)(String str) if (isNarrowString!String) { - return to!(typeof(return))(str); + import std.utf : toUTF32; + return cast(typeof(return)) str.toUTF32; } unittest { - static struct TestArray { int x; string toString() { return .to!string(x); } } + import std.conv : to; + + static struct TestArray { int x; string toString() { return to!string(x); } } static struct OpAssign { @@ -165,7 +228,7 @@ unittest { int x; this(int y) { x = y; } - override string toString() const { return .to!string(x); } + override string toString() const { return to!string(x); } } auto c = array([new C(1), new C(2)][]); //writeln(c); @@ -226,6 +289,7 @@ unittest // Bugzilla 10220 unittest { + import std.exception; import std.algorithm : equal; import std.range : repeat; @@ -252,14 +316,20 @@ unittest } /** -Returns a newly allocated associative array out of elements of the input range, -which must be a range of tuples (Key, Value). +Returns a newly allocated associative _array from a range of key/value tuples. +Params: r = An input range of tuples of keys and values. +Returns: A newly allocated associative array out of elements of the input +range, which must be a range of tuples (Key, Value). +See_Also: $(XREF typecons, Tuple) */ auto assocArray(Range)(Range r) - if (isInputRange!Range && isTuple!(ElementType!Range) && - ElementType!Range.length == 2) + if (isInputRange!Range && + ElementType!Range.length == 2 && + isMutable!(ElementType!Range.Types[1])) { + import std.typecons : isTuple; + static assert(isTuple!(ElementType!Range), "assocArray: argument must be a range of tuples"); alias KeyType = ElementType!Range.Types[0]; alias ValueType = ElementType!Range.Types[1]; ValueType[KeyType] aa; @@ -271,6 +341,8 @@ auto assocArray(Range)(Range r) /// /*@safe*/ pure /*nothrow*/ unittest { + import std.range; + import std.typecons; auto a = assocArray(zip([0, 1, 2], ["a", "b", "c"])); assert(is(typeof(a) == string[int])); assert(a == [0:"a", 1:"b", 2:"c"]); @@ -280,16 +352,90 @@ auto assocArray(Range)(Range r) assert(b == ["foo":"bar", "baz":"quux"]); } -/// @@@11053@@@ - Cannot be version(unittest) - recursive instantiation error +// @@@11053@@@ - Cannot be version(unittest) - recursive instantiation error unittest { + import std.typecons; static assert(!__traits(compiles, [ tuple("foo", "bar", "baz") ].assocArray())); static assert(!__traits(compiles, [ tuple("foo") ].assocArray())); static assert( __traits(compiles, [ tuple("foo", "bar") ].assocArray())); } +// Issue 13909 +unittest +{ + import std.typecons; + auto a = [tuple!(const string, string)("foo", "bar")]; + auto b = [tuple!(string, const string)("foo", "bar")]; + static assert( __traits(compiles, assocArray(a))); + static assert(!__traits(compiles, assocArray(b))); +} + +/** +Construct a range iterating over an associative array by key/value tuples. + +Params: aa = The associative array to iterate over. + +Returns: A forward range of Tuple's of key and value pairs from the given +associative array. +*/ +auto byPair(Key, Value)(Value[Key] aa) +{ + import std.typecons : tuple; + import std.algorithm : map; + + return aa.byKeyValue.map!(pair => tuple(pair.key, pair.value)); +} + +/// +unittest +{ + import std.typecons : tuple, Tuple; + import std.algorithm : sort; + + auto aa = ["a": 1, "b": 2, "c": 3]; + Tuple!(string, int)[] pairs; + + // Iteration over key/value pairs. + foreach (pair; aa.byPair) + { + pairs ~= pair; + } + + // Iteration order is implementation-dependent, so we should sort it to get + // a fixed order. + sort(pairs); + assert(pairs == [ + tuple("a", 1), + tuple("b", 2), + tuple("c", 3) + ]); +} + +unittest +{ + import std.typecons : tuple, Tuple; + + auto aa = ["a":2]; + auto pairs = aa.byPair(); + + static assert(is(typeof(pairs.front) == Tuple!(string,int))); + static assert(isForwardRange!(typeof(pairs))); + + assert(!pairs.empty); + assert(pairs.front == tuple("a", 2)); + + auto savedPairs = pairs.save; + + pairs.popFront(); + assert(pairs.empty); + assert(!savedPairs.empty); + assert(savedPairs.front == tuple("a", 2)); +} + private template blockAttribute(T) { + import core.memory; static if (hasIndirections!(T) || is(T == void)) { enum blockAttribute = 0; @@ -301,7 +447,8 @@ private template blockAttribute(T) } version(unittest) { - static assert(!(blockAttribute!void & GC.BlkAttr.NO_SCAN)); + import core.memory : UGC = GC; + static assert(!(blockAttribute!void & UGC.BlkAttr.NO_SCAN)); } // Returns the number of dimensions in an array T. @@ -339,8 +486,8 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I)) alias toSize_t(E) = size_t; static assert(allSatisfy!(isSize_t, I), - format("Argument types in %s are not all convertible to size_t: %s", - I.stringof, Filter!(templateNot!(isSize_t), I).stringof)); + "Argument types in "~I.stringof~" are not all convertible to size_t: " + ~Filter!(templateNot!(isSize_t), I).stringof); //Eagerlly transform non-size_t into size_t to avoid template bloat alias ST = staticMap!(toSize_t, I); @@ -374,9 +521,8 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I)) alias toSize_t(E) = size_t; static assert(allSatisfy!(isSize_t, I), - format("Argument types in %s are not all convertible to size_t: %s", - I.stringof, Filter!(templateNot!(isSize_t), I).stringof)); - + "Argument types in "~I.stringof~" are not all convertible to size_t: " + ~Filter!(templateNot!(isSize_t), I).stringof); //Eagerlly transform non-size_t into size_t to avoid template bloat alias ST = staticMap!(toSize_t, I); @@ -400,7 +546,7 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I)) private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow { static assert(I.length <= nDimensions!T, - format("%s dimensions specified for a %s dimensional array.", I.length, nDimensions!T)); + I.length.stringof~"dimensions specified for a "~nDimensions!T.stringof~" dimensional array."); alias E = ElementEncodingType!T; @@ -437,6 +583,7 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow else { import core.stdc.string : memset; + import core.memory; auto ptr = cast(E*) GC.malloc(sizes[0] * E.sizeof, blockAttribute!E); static if (minimallyInitialized && hasIndirections!E) memset(ptr, 0, size * E.sizeof); @@ -521,291 +668,6 @@ nothrow unittest enum b3 = minimallyInitializedArray!(S3[][])(2, 2); } -/** -Implements the range interface primitive $(D empty) for built-in -arrays. Due to the fact that nonmember functions can be called with -the first argument using the dot notation, $(D array.empty) is -equivalent to $(D empty(array)). - */ - -@property bool empty(T)(in T[] a) @safe pure nothrow -{ - return !a.length; -} - -/// -@safe pure nothrow unittest -{ - auto a = [ 1, 2, 3 ]; - assert(!a.empty); - assert(a[3 .. $].empty); -} - -/** -Implements the range interface primitive $(D save) for built-in -arrays. Due to the fact that nonmember functions can be called with -the first argument using the dot notation, $(D array.save) is -equivalent to $(D save(array)). The function does not duplicate the -content of the array, it simply returns its argument. - */ - -@property T[] save(T)(T[] a) @safe pure nothrow -{ - return a; -} - -/// -@safe pure nothrow unittest -{ - auto a = [ 1, 2, 3 ]; - auto b = a.save; - assert(b is a); -} -/** -Implements the range interface primitive $(D popFront) for built-in -arrays. Due to the fact that nonmember functions can be called with -the first argument using the dot notation, $(D array.popFront) is -equivalent to $(D popFront(array)). For $(GLOSSARY narrow strings), -$(D popFront) automatically advances to the next $(GLOSSARY code -point). -*/ - -void popFront(T)(ref T[] a) @safe pure nothrow -if (!isNarrowString!(T[]) && !is(T[] == void[])) -{ - assert(a.length, "Attempting to popFront() past the end of an array of " ~ T.stringof); - a = a[1 .. $]; -} - -/// -@safe pure nothrow unittest -{ - auto a = [ 1, 2, 3 ]; - a.popFront(); - assert(a == [ 2, 3 ]); -} - -version(unittest) -{ - static assert(!is(typeof({ int[4] a; popFront(a); }))); - static assert(!is(typeof({ immutable int[] a; popFront(a); }))); - static assert(!is(typeof({ void[] a; popFront(a); }))); -} - -// Specialization for narrow strings. The necessity of -void popFront(C)(ref C[] str) @trusted pure nothrow -if (isNarrowString!(C[])) -{ - assert(str.length, "Attempting to popFront() past the end of an array of " ~ C.stringof); - - static if(is(Unqual!C == char)) - { - immutable c = str[0]; - if(c < 0x80) - { - //ptr is used to avoid unnnecessary bounds checking. - str = str.ptr[1 .. str.length]; - } - else - { - import core.bitop; - auto msbs = 7 - bsr(~c); - if((msbs < 2) | (msbs > 6)) - { - //Invalid UTF-8 - msbs = 1; - } - str = str[msbs .. $]; - } - } - else static if(is(Unqual!C == wchar)) - { - immutable u = str[0]; - str = str[1 + (u >= 0xD800 && u <= 0xDBFF) .. $]; - } - else static assert(0, "Bad template constraint."); -} - -@safe pure unittest -{ - foreach(S; TypeTuple!(string, wstring, dstring)) - { - S s = "\xC2\xA9hello"; - s.popFront(); - assert(s == "hello"); - - S str = "hello\U00010143\u0100\U00010143"; - foreach(dchar c; ['h', 'e', 'l', 'l', 'o', '\U00010143', '\u0100', '\U00010143']) - { - assert(str.front == c); - str.popFront(); - } - assert(str.empty); - - static assert(!is(typeof({ immutable S a; popFront(a); }))); - static assert(!is(typeof({ typeof(S.init[0])[4] a; popFront(a); }))); - } - - C[] _eatString(C)(C[] str) - { - while(!str.empty) - str.popFront(); - - return str; - } - enum checkCTFE = _eatString("ウェブサイト@La_Verité.com"); - static assert(checkCTFE.empty); - enum checkCTFEW = _eatString("ウェブサイト@La_Verité.com"w); - static assert(checkCTFEW.empty); -} - -/** -Implements the range interface primitive $(D popBack) for built-in -arrays. Due to the fact that nonmember functions can be called with -the first argument using the dot notation, $(D array.popBack) is -equivalent to $(D popBack(array)). For $(GLOSSARY narrow strings), $(D -popFront) automatically eliminates the last $(GLOSSARY code point). -*/ - -void popBack(T)(ref T[] a) @safe pure nothrow -if (!isNarrowString!(T[]) && !is(T[] == void[])) -{ - assert(a.length); - a = a[0 .. $ - 1]; -} - -/// -@safe pure nothrow unittest -{ - auto a = [ 1, 2, 3 ]; - a.popBack(); - assert(a == [ 1, 2 ]); -} - -version(unittest) -{ - static assert(!is(typeof({ immutable int[] a; popBack(a); }))); - static assert(!is(typeof({ int[4] a; popBack(a); }))); - static assert(!is(typeof({ void[] a; popBack(a); }))); -} - -// Specialization for arrays of char -void popBack(T)(ref T[] a) @safe pure -if (isNarrowString!(T[])) -{ - assert(a.length, "Attempting to popBack() past the front of an array of " ~ T.stringof); - a = a[0 .. $ - std.utf.strideBack(a, $)]; -} - -@safe pure unittest -{ - foreach(S; TypeTuple!(string, wstring, dstring)) - { - S s = "hello\xE2\x89\xA0"; - s.popBack(); - assert(s == "hello"); - S s3 = "\xE2\x89\xA0"; - auto c = s3.back; - assert(c == cast(dchar)'\u2260'); - s3.popBack(); - assert(s3 == ""); - - S str = "\U00010143\u0100\U00010143hello"; - foreach(dchar ch; ['o', 'l', 'l', 'e', 'h', '\U00010143', '\u0100', '\U00010143']) - { - assert(str.back == ch); - str.popBack(); - } - assert(str.empty); - - static assert(!is(typeof({ immutable S a; popBack(a); }))); - static assert(!is(typeof({ typeof(S.init[0])[4] a; popBack(a); }))); - } -} - -/** -Implements the range interface primitive $(D front) for built-in -arrays. Due to the fact that nonmember functions can be called with -the first argument using the dot notation, $(D array.front) is -equivalent to $(D front(array)). For $(GLOSSARY narrow strings), $(D -front) automatically returns the first $(GLOSSARY code point) as a $(D -dchar). -*/ -@property ref T front(T)(T[] a) @safe pure nothrow -if (!isNarrowString!(T[]) && !is(T[] == void[])) -{ - assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); - return a[0]; -} - -/// -@safe pure nothrow unittest -{ - int[] a = [ 1, 2, 3 ]; - assert(a.front == 1); -} - -@safe pure nothrow unittest -{ - auto a = [ 1, 2 ]; - a.front = 4; - assert(a.front == 4); - assert(a == [ 4, 2 ]); - - immutable b = [ 1, 2 ]; - assert(b.front == 1); - - int[2] c = [ 1, 2 ]; - assert(c.front == 1); -} - -@property dchar front(T)(T[] a) @safe pure if (isNarrowString!(T[])) -{ - assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); - size_t i = 0; - return decode(a, i); -} - -/** -Implements the range interface primitive $(D back) for built-in -arrays. Due to the fact that nonmember functions can be called with -the first argument using the dot notation, $(D array.back) is -equivalent to $(D back(array)). For $(GLOSSARY narrow strings), $(D -back) automatically returns the last $(GLOSSARY code point) as a $(D -dchar). -*/ -@property ref T back(T)(T[] a) @safe pure nothrow if (!isNarrowString!(T[])) -{ - assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof); - return a[$ - 1]; -} - -/// -@safe pure nothrow unittest -{ - int[] a = [ 1, 2, 3 ]; - assert(a.back == 3); - a.back += 4; - assert(a.back == 7); -} - -@safe pure nothrow unittest -{ - immutable b = [ 1, 2, 3 ]; - assert(b.back == 3); - - int[3] c = [ 1, 2, 3 ]; - assert(c.back == 3); -} - -// Specialization for strings -@property dchar back(T)(T[] a) @safe pure if (isNarrowString!(T[])) -{ - assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof); - size_t i = a.length - std.utf.strideBack(a, a.length); - return decode(a, i); -} - // overlap /* NOTE: Undocumented for now, overlap does not yet work with ctfe. @@ -840,6 +702,7 @@ inout(T)[] overlap(T)(inout(T)[] r1, inout(T)[] r2) @trusted pure nothrow { static void test(L, R)(L l, R r) { + import std.stdio; scope(failure) writeln("Types: L %s R %s", L.stringof, R.stringof); assert(overlap(l, r) == [ 100, 12 ]); @@ -908,6 +771,7 @@ T[] insert(T, Range)(T[] array, size_t pos, Range stuff) { static if(hasLength!Range && is(ElementEncodingType!Range : T)) { + import std.algorithm : copy; auto retval = new Unqual!(T)[](array.length + stuff.length); retval[0 .. pos] = array[0 .. pos]; copy(stuff, retval[pos .. pos + stuff.length]); @@ -945,6 +809,11 @@ unittest unittest { + import core.exception; + import std.conv : to; + import std.exception; + import std.algorithm; + auto a = [1, 2, 3, 4]; assert(a.insert(0, [6, 7]) == [6, 7, 1, 2, 3, 4]); assert(a.insert(2, [6, 7]) == [1, 2, 6, 7, 3, 4]); @@ -1017,14 +886,6 @@ private void copyBackwards(T)(T[] src, T[] dest) Inserts $(D stuff) (which must be an input range or any number of implicitly convertible items) in $(D array) at position $(D pos). - Example: - --- - int[] a = [ 1, 2, 3, 4 ]; - a.insertInPlace(2, [ 1, 2 ]); - assert(a == [ 1, 2, 1, 2, 3, 4 ]); - a.insertInPlace(3, 10u, 11); - assert(a == [ 1, 2, 1, 10, 11, 2, 3, 4]); - --- +/ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) if(!isSomeString!(T[]) @@ -1033,16 +894,18 @@ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) static if(allSatisfy!(isInputRangeWithLengthOrConvertible!T, U)) { import core.stdc.string; - auto trustedAllocateArray(size_t n) @trusted nothrow + import std.conv : emplaceRef; + + static auto trustedAllocateArray(size_t n) @trusted nothrow { return uninitializedArray!(T[])(n); } - void trustedMemcopy(T[] dest, T[] src) @trusted + + static void trustedMemcopy(T[] dest, T[] src) @trusted { assert(src.length == dest.length); if (!__ctfe) { - import core.stdc.string : memcpy; memcpy(dest.ptr, src.ptr, src.length * T.sizeof); } else @@ -1094,13 +957,14 @@ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) } } -/++ Ditto +/ +/// Ditto void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) if(isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) { static if(is(Unqual!T == T) && allSatisfy!(isInputRangeWithLengthOrConvertible!dchar, U)) { + import std.utf : codeLength; // mutable, can do in place //helper function: re-encode dchar to Ts and store at *ptr static T* putDChar(T* ptr, dchar ch) @@ -1112,6 +976,7 @@ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) } else { + import std.utf : encode; T[dchar.sizeof/T.sizeof] buf; size_t len = encode(buf, ch); final switch(len) @@ -1155,7 +1020,7 @@ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) ptr = putDChar(ptr, ch); } } - assert(ptr == array.ptr + pos + to_insert, text(ptr - array.ptr, " vs ", pos + to_insert )); + assert(ptr == array.ptr + pos + to_insert, "(ptr == array.ptr + pos + to_insert) is false"); } else { @@ -1169,6 +1034,16 @@ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) } } +/// +@safe pure unittest +{ + int[] a = [ 1, 2, 3, 4 ]; + a.insertInPlace(2, [ 1, 2 ]); + assert(a == [ 1, 2, 1, 2, 3, 4 ]); + a.insertInPlace(3, 10u, 11); + assert(a == [ 1, 2, 1, 10, 11, 2, 3, 4]); +} + //constraint helpers private template isInputRangeWithLengthOrConvertible(E) { @@ -1198,19 +1073,14 @@ private template isInputRangeOrConvertible(E) } } - -//Verify Example. -@safe unittest -{ - int[] a = [ 1, 2, 3, 4 ]; - a.insertInPlace(2, [ 1, 2 ]); - assert(a == [ 1, 2, 1, 2, 3, 4 ]); - a.insertInPlace(3, 10u, 11); - assert(a == [ 1, 2, 1, 10, 11, 2, 3, 4]); -} - unittest { + import core.exception; + import std.conv : to; + import std.exception; + import std.algorithm; + + bool test(T, U, V)(T orig, size_t pos, U toInsert, V result, string file = __FILE__, size_t line = __LINE__) { @@ -1299,6 +1169,7 @@ unittest unittest { + import std.algorithm : equal; // insertInPlace interop with postblit struct Int { @@ -1331,6 +1202,7 @@ unittest @safe unittest { + import std.exception; assertCTFEable!( { int[] a = [1, 2]; @@ -1342,6 +1214,7 @@ unittest unittest // bugzilla 6874 { + import core.memory; // allocate some space byte[] a; a.length = 1; @@ -1437,14 +1310,18 @@ ElementEncodingType!S[] replicate(S)(S s, size_t n) if (isDynamicArray!S) return cast(RetType) r; } +/// ditto ElementType!S[] replicate(S)(S s, size_t n) if (isInputRange!S && !isDynamicArray!S) { + import std.range : repeat; return join(std.range.repeat(s, n)); } unittest { + import std.conv : to; + debug(std_array) printf("array.replicate.unittest\n"); foreach (S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[])) @@ -1467,6 +1344,12 @@ Eagerly split the string $(D s) into an array of words, using whitespace as delimiter. Runs of whitespace are merged together (no empty words are produced). $(D @safe), $(D pure) and $(D CTFE)-able. + +See_Also: +$(XREF algorithm, splitter) for a version that splits using any separator. + +$(XREF regex, splitter) for a version that splits using a regular +expression defined separator. +/ S[] split(S)(S s) @safe pure if (isSomeString!S) @@ -1502,6 +1385,10 @@ if (isSomeString!S) unittest { + import std.conv : to; + import std.format; + import std.typecons; + static auto makeEntry(S)(string l, string[] r) {return tuple(l.to!S(), r.to!(S[])());} @@ -1530,6 +1417,7 @@ unittest unittest //safety, purity, ctfe ... { + import std.exception; void dg() @safe pure { assert(split("hello world"c) == ["hello"c, "world"c]); assert(split("hello world"w) == ["hello"w, "world"w]); @@ -1540,49 +1428,61 @@ unittest //safety, purity, ctfe ... } /++ -Alias for $(XREF algorithm, splitter). +Alias for $(XREF algorithm, _splitter). +/ -alias splitter = std.algorithm.splitter; +deprecated("Please use std.algorithm.iteration.splitter instead.") +alias splitter = std.algorithm.iteration.splitter; /++ -Eagerly splits $(D s) into an array, using $(D delim) as the delimiter. + Eagerly splits $(D range) into an array, using $(D sep) as the delimiter. + + The range must be a $(XREF2 range, isForwardRange, forward range). + The separator can be a value of the same type as the elements in $(D range) or + it can be another forward range. + + Examples: + If $(D range) is a $(D string), $(D sep) can be a $(D char) or another + $(D string). The return type will be an array of strings. If $(D range) is + an $(D int) array, $(D sep) can be an $(D int) or another $(D int) array. + The return type will be an array of $(D int) arrays. -See also: $(XREF algorithm, splitter) for the lazy version of this operator. + Params: + range = a forward range. + sep = a value of the same type as the elements of $(D range) or another + forward range. + + Returns: + An array containing the divided parts of $(D range). + + See_Also: + $(XREF algorithm, splitter) for the lazy version of this function. +/ -auto split(R, E)(R r, E delim) -if (isForwardRange!R && is(typeof(ElementType!R.init == E.init))) -{ - auto spl = std.algorithm.splitter(r, delim); - alias S = typeof(spl.front.init); // "Slice_t" - auto app = appender!(S[])(); - foreach (e; spl) - app.put(e); - return app.data; +auto split(Range, Separator)(Range range, Separator sep) +if (isForwardRange!Range && is(typeof(ElementType!Range.init == Separator.init))) +{ + import std.algorithm : splitter; + return range.splitter(sep).array; } -auto split(R1, R2)(R1 r, R2 delim) -if (isForwardRange!R1 && isForwardRange!R2 && is(typeof(ElementType!R1.init == ElementType!R2.init))) +///ditto +auto split(Range, Separator)(Range range, Separator sep) +if (isForwardRange!Range && isForwardRange!Separator && is(typeof(ElementType!Range.init == ElementType!Separator.init))) { - auto spl = std.algorithm.splitter(r, delim); - alias S = typeof(spl.front.init); // "Slice_t" - auto app = appender!(S[])(); - foreach (e; spl) - app.put(e); - return app.data; + import std.algorithm : splitter; + return range.splitter(sep).array; } ///ditto -auto split(alias isTerminator, R)(R r) -if (isForwardRange!R && is(typeof(unaryFun!isTerminator(r.front)))) -{ - auto spl = std.algorithm.splitter!isTerminator(r); - alias S = typeof(spl.front.init); // "Slice_t" - auto app = appender!(S[])(); - foreach (e; spl) - app.put(e); - return app.data; +auto split(alias isTerminator, Range)(Range range) +if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(range.front)))) +{ + import std.algorithm : splitter; + return range.splitter!isTerminator.array; } unittest { + import std.conv; + import std.algorithm : cmp; + debug(std_array) printf("array.split\n"); foreach (S; TypeTuple!(string, wstring, dstring, immutable(string), immutable(wstring), immutable(dstring), @@ -1632,10 +1532,29 @@ unittest } } +/++ + Conservative heuristic to determine if a range can be iterated cheaply. + Used by $(D join) in decision to do an extra iteration of the range to + compute the resultant length. If iteration is not cheap then precomputing + length could be more expensive than using $(D Appender). + + For now, we only assume arrays are cheap to iterate. + +/ +private enum bool hasCheapIteration(R) = isArray!R; /++ Concatenates all of the ranges in $(D ror) together into one array using $(D sep) as the separator if present. + + Params: + ror = Range of Ranges of Elements + sep = Range of Elements + + Returns: + an allocated array of Elements + + See_Also: + $(XREF algorithm, joiner) +/ ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, R sep) if(isInputRange!RoR && @@ -1643,8 +1562,9 @@ ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, R sep) isInputRange!R && is(Unqual!(ElementType!(ElementType!RoR)) == Unqual!(ElementType!R))) { - alias RoRElem = ElementType!RoR; alias RetType = typeof(return); + alias RetTypeElement = Unqual!(ElementEncodingType!RetType); + alias RoRElem = ElementType!RoR; if (ror.empty) return RetType.init; @@ -1653,34 +1573,110 @@ ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, R sep) // This converts sep to an array (forward range) if it isn't one, // and makes sure it has the same string encoding for string types. static if (isSomeString!RetType && - !is(Unqual!(ElementEncodingType!RetType) == Unqual!(ElementEncodingType!R))) + !is(RetTypeElement == Unqual!(ElementEncodingType!R))) + { + import std.conv : to; auto sepArr = to!RetType(sep); + } else static if (!isArray!R) auto sepArr = array(sep); else alias sepArr = sep; - auto result = appender!RetType(); - static if(isForwardRange!RoR && - (isNarrowString!RetType || hasLength!RoRElem)) + static if(hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) + { + import std.conv : emplaceRef; + size_t length; + foreach(r; ror.save) + length += r.length + sepArr.length; + length -= sepArr.length; + auto result = uninitializedArray!(RetTypeElement[])(length); + size_t len; + foreach(e; ror.front) + emplaceRef(result[len++], e); + ror.popFront(); + foreach(r; ror) + { + foreach(e; sepArr) + emplaceRef(result[len++], e); + foreach(e; r) + emplaceRef(result[len++], e); + } + assert(len == result.length); + static U trustedCast(U, V)(V v) @trusted { return cast(U) v; } + return trustedCast!RetType(result); + } + else + { + auto result = appender!RetType(); + put(result, ror.front); + ror.popFront(); + for (; !ror.empty; ror.popFront()) + { + put(result, sep); + put(result, ror.front); + } + return result.data; + } +} + +/// Ditto +ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, E sep) + if(isInputRange!RoR && + isInputRange!(Unqual!(ElementType!RoR)) && + is(E : ElementType!(ElementType!RoR))) +{ + alias RetType = typeof(return); + alias RetTypeElement = Unqual!(ElementEncodingType!RetType); + alias RoRElem = ElementType!RoR; + + if (ror.empty) + return RetType.init; + + static if(hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) { - // Reserve appender length if it can be computed. - size_t resultLen = 0; - immutable sepArrLength = sepArr.length; - for (auto temp = ror.save; !temp.empty; temp.popFront()) - resultLen += temp.front.length + sepArrLength; - resultLen -= sepArrLength; - result.reserve(resultLen); - version(unittest) scope(exit) assert(result.data.length == resultLen); + static if (isSomeChar!E && isSomeChar!RetTypeElement && E.sizeof > RetTypeElement.sizeof) + { + import std.utf : encode; + RetTypeElement[4 / RetTypeElement.sizeof] encodeSpace; + immutable size_t sepArrLength = encode(encodeSpace, sep); + return join(ror, encodeSpace[0..sepArrLength]); + } + else + { + import std.conv : emplaceRef; + size_t length; + foreach(r; ror.save) + length += r.length + 1; + length -= 1; + auto result = uninitializedArray!(RetTypeElement[])(length); + size_t len; + foreach(e; ror.front) + emplaceRef(result[len++], e); + ror.popFront(); + foreach(r; ror) + { + emplaceRef(result[len++], sep); + foreach(e; r) + emplaceRef(result[len++], e); + } + assert(len == result.length); + static U trustedCast(U, V)(V v) @trusted { return cast(U) v; } + return trustedCast!RetType(result); + } } - put(result, ror.front); - ror.popFront(); - for (; !ror.empty; ror.popFront()) + else { - put(result, sepArr); + auto result = appender!RetType(); put(result, ror.front); + ror.popFront(); + for (; !ror.empty; ror.popFront()) + { + put(result, sep); + put(result, ror.front); + } + return result.data; } - return result.data; } /// Ditto @@ -1689,22 +1685,34 @@ ElementEncodingType!(ElementType!RoR)[] join(RoR)(RoR ror) isInputRange!(Unqual!(ElementType!RoR))) { alias RetType = typeof(return); + alias RetTypeElement = Unqual!(ElementEncodingType!RetType); + alias RoRElem = ElementType!RoR; if (ror.empty) return RetType.init; - alias R = ElementType!RoR; - auto result = appender!RetType(); - static if(isForwardRange!RoR && (hasLength!R || isNarrowString!R)) + static if(hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) + { + import std.conv : emplaceRef; + size_t length; + foreach(r; ror.save) + length += r.length; + auto result = uninitializedArray!(RetTypeElement[])(length); + size_t len; + foreach(r; ror) + foreach(e; r) + emplaceRef(result[len++], e); + assert(len == result.length); + static U trustedCast(U, V)(V v) @trusted { return cast(U) v; } + return trustedCast!RetType(result); + } + else { - // Reserve appender length if it can be computed. - immutable resultLen = reduce!("a + b.length")(cast(size_t) 0, ror.save); - result.reserve(resultLen); - version(unittest) scope(exit) assert(result.data.length == resultLen); + auto result = appender!RetType(); + for (; !ror.empty; ror.popFront()) + put(result, ror.front); + return result.data; } - for (; !ror.empty; ror.popFront()) - put(result, ror.front); - return result.data; } /// @@ -1721,8 +1729,51 @@ ElementEncodingType!(ElementType!RoR)[] join(RoR)(RoR ror) assert(arr.join() == "applebanana"); } +@safe pure unittest +{ + import std.conv : to; + + foreach (T; TypeTuple!(string,wstring,dstring)) + { + auto arr2 = "Здравствуй Мир Unicode".to!(T); + auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]); + assert(join(arr) == "ЗдравствуйМирUnicode"); + foreach (S; TypeTuple!(char,wchar,dchar)) + { + auto jarr = arr.join(to!S(' ')); + static assert(is(typeof(jarr) == T)); + assert(jarr == arr2); + } + foreach (S; TypeTuple!(string,wstring,dstring)) + { + auto jarr = arr.join(to!S(" ")); + static assert(is(typeof(jarr) == T)); + assert(jarr == arr2); + } + } + + foreach (T; TypeTuple!(string,wstring,dstring)) + { + auto arr2 = "Здравствуй\u047CМир\u047CUnicode".to!(T); + auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]); + foreach (S; TypeTuple!(wchar,dchar)) + { + auto jarr = arr.join(to!S('\u047C')); + static assert(is(typeof(jarr) == T)); + assert(jarr == arr2); + } + } + + const string[] arr = ["apple", "banana"]; + assert(arr.join(',') == "apple,banana"); +} + unittest { + import std.conv : to; + import std.algorithm; + import std.range; + debug(std_array) printf("array.join.unittest\n"); foreach(R; TypeTuple!(string, wstring, dstring)) @@ -1743,9 +1794,12 @@ unittest auto filteredWords = filter!"true"(filteredWordsArr); foreach(S; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(join(filteredWords, to!S(", ")) == "日本語, paul, jerry"); + assert(join(filteredWords, to!(ElementType!S)(',')) == "日本語,paul,jerry"); + assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry"); assert(join(filteredWordsArr, to!S(", ")) == "日本語, paul, jerry"); + assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry"); assert(join(filteredLenWordsArr, to!S(", ")) == "日本語, paul, jerry"); assert(join(filter!"true"(words), to!S(", ")) == "日本語, paul, jerry"); assert(join(words, to!S(", ")) == "日本語, paul, jerry"); @@ -1774,7 +1828,7 @@ unittest assert(join(filteredLenWordsArr, filterComma) == "日本語, paul, jerry"); assert(join(filter!"true"(words), filterComma) == "日本語, paul, jerry"); assert(join(words, filterComma) == "日本語, paul, jerry"); - } + }(); assert(join(filteredWords) == "日本語pauljerry"); assert(join(filteredWordsArr) == "日本語pauljerry"); @@ -1821,6 +1875,36 @@ unittest assert(join(f([f([1, 2]), f([41, 42])]), f([5, 6])) == [1, 2, 5, 6, 41, 42]); } +// Issue 10683 +unittest +{ + import std.range : join; + import std.typecons : tuple; + assert([[tuple(1)]].join == [tuple(1)]); + assert([[tuple("x")]].join == [tuple("x")]); +} + +// Issue 13877 +unittest +{ + // Test that the range is iterated only once. + import std.algorithm : map; + int c = 0; + auto j1 = [1, 2, 3].map!(_ => [c++]).join; + assert(c == 3); + assert(j1 == [0, 1, 2]); + + c = 0; + auto j2 = [1, 2, 3].map!(_ => [c++]).join(9); + assert(c == 3); + assert(j2 == [0, 9, 1, 9, 2]); + + c = 0; + auto j3 = [1, 2, 3].map!(_ => [c++]).join([9]); + assert(c == 3); + assert(j3 == [0, 9, 1, 9, 2]); +} + /++ Replace occurrences of $(D from) with $(D to) in $(D subject). Returns a new @@ -1845,6 +1929,13 @@ if (isDynamicArray!(E[]) && isForwardRange!R1 && isForwardRange!R2 return app.data; } +/// +unittest +{ + assert("Hello Wörld".replace("o Wö", "o Wo") == "Hello World"); + assert("Hello Wörld".replace("l", "h") == "Hehho Wörhd"); +} + /++ Same as above, but outputs the result via OutputRange $(D sink). If no match is found the original array is transferred to $(D sink) as is. @@ -1875,25 +1966,31 @@ if (isOutputRange!(Sink, E) && isDynamicArray!(E[]) unittest { + import std.conv : to; + import std.algorithm : cmp; + debug(std_array) printf("array.replace.unittest\n"); foreach (S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[])) { - auto s = to!S("This is a foo foo list"); - auto from = to!S("foo"); - auto into = to!S("silly"); - S r; - int i; + foreach (T; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[])) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + auto s = to!S("This is a foo foo list"); + auto from = to!T("foo"); + auto into = to!S("silly"); + S r; + int i; - r = replace(s, from, into); - i = cmp(r, "This is a silly silly list"); - assert(i == 0); + r = replace(s, from, into); + i = cmp(r, "This is a silly silly list"); + assert(i == 0); - r = replace(s, to!S(""), into); - i = cmp(r, "This is a foo foo list"); - assert(i == 0); + r = replace(s, to!S(""), into); + i = cmp(r, "This is a foo foo list"); + assert(i == 0); - assert(replace(r, to!S("won't find this"), to!S("whatever")) is r); + assert(replace(r, to!S("won't find this"), to!S("whatever")) is r); + }(); } immutable s = "This is a foo foo list"; @@ -1902,6 +1999,9 @@ unittest unittest { + import std.conv : to; + import std.algorithm : skipOver; + struct CheckOutput(C) { C[] desired; @@ -1923,14 +2023,6 @@ unittest Replaces elements from $(D array) with indices ranging from $(D from) (inclusive) to $(D to) (exclusive) with the range $(D stuff). Returns a new array without changing the contents of $(D subject). - - Examples: - -------------------- - auto a = [ 1, 2, 3, 4 ]; - auto b = a.replace(1, 3, [ 9, 9, 9 ]); - assert(a == [ 1, 2, 3, 4 ]); - assert(b == [ 1, 9, 9, 9, 4 ]); - -------------------- +/ T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff) if(isInputRange!Range && @@ -1939,6 +2031,7 @@ T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff) { static if(hasLength!Range && is(ElementEncodingType!Range : T)) { + import std.algorithm : copy; assert(from <= to); immutable sliceLen = to - from; auto retval = new Unqual!(T)[](subject.length - sliceLen + stuff.length); @@ -1960,7 +2053,7 @@ T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff) } } -//Verify Examples. +/// unittest { auto a = [ 1, 2, 3, 4 ]; @@ -1971,6 +2064,12 @@ unittest unittest { + import core.exception; + import std.conv : to; + import std.exception; + import std.algorithm; + + auto a = [ 1, 2, 3, 4 ]; assert(replace(a, 0, 0, [5, 6, 7]) == [5, 6, 7, 1, 2, 3, 4]); assert(replace(a, 0, 2, cast(int[])[]) == [3, 4]); @@ -2040,13 +2139,6 @@ unittest Replaces elements from $(D array) with indices ranging from $(D from) (inclusive) to $(D to) (exclusive) with the range $(D stuff). Expands or shrinks the array as needed. - - Example: - --- - int[] a = [ 1, 2, 3, 4 ]; - a.replaceInPlace(1, 3, [ 9, 9, 9 ]); - assert(a == [ 1, 9, 9, 9, 4 ]); - --- +/ void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) if(isDynamicArray!Range && @@ -2054,6 +2146,9 @@ void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff !is(T == const T) && !is(T == immutable T)) { + import std.algorithm : remove; + import std.typecons : tuple; + if (overlap(array, stuff).length) { // use slower/conservative method @@ -2077,6 +2172,7 @@ void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff } } +/// Ditto void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) if(isInputRange!Range && ((!isDynamicArray!Range && is(ElementType!Range : T)) || @@ -2087,7 +2183,7 @@ void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff array = replace(array, from, to, stuff); } -//Verify Examples. +/// unittest { int[] a = [1, 4, 5]; @@ -2110,6 +2206,12 @@ unittest unittest { + import core.exception; + import std.conv : to; + import std.exception; + import std.algorithm; + + bool test(T, U, V)(T orig, size_t from, size_t to, U toReplace, V result, string file = __FILE__, size_t line = __LINE__) { @@ -2184,40 +2286,85 @@ if (isDynamicArray!(E[]) && isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) && isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1])))) { + import std.algorithm : countUntil; if (from.empty) return subject; - auto balance = std.algorithm.find(subject, from.save); - if (balance.empty) return subject; + static if (isSomeString!(E[])) + { + import std.string : indexOf; + immutable idx = subject.indexOf(from); + } + else + { + import std.algorithm : countUntil; + immutable idx = subject.countUntil(from); + } + if (idx == -1) + return subject; + auto app = appender!(E[])(); - app.put(subject[0 .. subject.length - balance.length]); - app.put(to.save); - app.put(balance[from.length .. $]); + app.put(subject[0 .. idx]); + app.put(to); + + static if (isSomeString!(E[]) && isSomeString!R1) + { + import std.utf : codeLength; + immutable fromLength = codeLength!(Unqual!E, R1)(from); + } + else + immutable fromLength = from.length; + + app.put(subject[idx + fromLength .. $]); return app.data; } +/// +unittest +{ + auto a = [1, 2, 2, 3, 4, 5]; + auto b = a.replaceFirst([2], [1337]); + assert(b == [1, 1337, 2, 3, 4, 5]); + + auto s = "This is a foo foo list"; + auto r = s.replaceFirst("foo", "silly"); + assert(r == "This is a silly foo list"); +} + unittest { + import std.conv : to; + import std.algorithm : cmp; + debug(std_array) printf("array.replaceFirst.unittest\n"); - foreach(S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[], + foreach (S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[], const(char[]), immutable(char[]))) { - alias T = Unqual!S; + foreach (T; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[], + const(char[]), immutable(char[]))) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + auto s = to!S("This is a foo foo list"); + auto s2 = to!S("Thüs is a ßöö foo list"); + auto from = to!T("foo"); + auto from2 = to!T("ßöö"); + auto into = to!T("silly"); + auto into2 = to!T("sälly"); - auto s = to!S("This is a foo foo list"); - auto from = to!T("foo"); - auto into = to!T("silly"); + S r1 = replaceFirst(s, from, into); + assert(cmp(r1, "This is a silly foo list") == 0); - S r1 = replaceFirst(s, from, into); - assert(cmp(r1, "This is a silly foo list") == 0); + S r11 = replaceFirst(s2, from2, into2); + assert(cmp(r11, "Thüs is a sälly foo list") == 0, + to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof); - S r2 = replaceFirst(r1, from, into); - assert(cmp(r2, "This is a silly silly list") == 0); + S r2 = replaceFirst(r1, from, into); + assert(cmp(r2, "This is a silly silly list") == 0); - S r3 = replaceFirst(s, to!T(""), into); - assert(cmp(r3, "This is a foo foo list") == 0); + S r3 = replaceFirst(s, to!T(""), into); + assert(cmp(r3, "This is a foo foo list") == 0); - assert(replaceFirst(r3, to!T("won't find"), to!T("whatever")) is r3); + assert(replaceFirst(r3, to!T("won't find"), to!T("whatever")) is r3); + }(); } } @@ -2229,6 +2376,106 @@ unittest assert(replaceFirst(res, "a", "b") == ["b", "a"]); } +/++ + Replaces the last occurrence of $(D from) with $(D to) in $(D a). Returns a + new array without changing the contents of $(D subject), or the original + array if no match is found. + +/ +E[] replaceLast(E, R1, R2)(E[] subject, R1 from , R2 to) +if (isDynamicArray!(E[]) && + isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) && + isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1])))) +{ + import std.range : retro; + if (from.empty) return subject; + static if (isSomeString!(E[])) + { + import std.string : lastIndexOf; + auto idx = subject.lastIndexOf(from); + } + else + { + import std.algorithm : countUntil; + auto idx = retro(subject).countUntil(retro(from)); + } + + if (idx == -1) + return subject; + + static if (isSomeString!(E[]) && isSomeString!R1) + { + import std.utf : codeLength; + auto fromLength = codeLength!(Unqual!E, R1)(from); + } + else + auto fromLength = from.length; + + auto app = appender!(E[])(); + static if (isSomeString!(E[])) + app.put(subject[0 .. idx]); + else + app.put(subject[0 .. $ - idx - fromLength]); + + app.put(to); + + static if (isSomeString!(E[])) + app.put(subject[idx+fromLength .. $]); + else + app.put(subject[$ - idx .. $]); + + return app.data; +} + +/// +unittest +{ + auto a = [1, 2, 2, 3, 4, 5]; + auto b = a.replaceLast([2], [1337]); + assert(b == [1, 2, 1337, 3, 4, 5]); + + auto s = "This is a foo foo list"; + auto r = s.replaceLast("foo", "silly"); + assert(r == "This is a foo silly list", r); +} + +unittest +{ + import std.conv : to; + import std.algorithm : cmp; + + debug(std_array) printf("array.replaceLast.unittest\n"); + + foreach (S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[], + const(char[]), immutable(char[]))) + { + foreach (T; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[], + const(char[]), immutable(char[]))) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + auto s = to!S("This is a foo foo list"); + auto s2 = to!S("Thüs is a ßöö ßöö list"); + auto from = to!T("foo"); + auto from2 = to!T("ßöö"); + auto into = to!T("silly"); + auto into2 = to!T("sälly"); + + S r1 = replaceLast(s, from, into); + assert(cmp(r1, "This is a foo silly list") == 0, to!string(r1)); + + S r11 = replaceLast(s2, from2, into2); + assert(cmp(r11, "Thüs is a ßöö sälly list") == 0, + to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof); + + S r2 = replaceLast(r1, from, into); + assert(cmp(r2, "This is a silly silly list") == 0); + + S r3 = replaceLast(s, to!T(""), into); + assert(cmp(r3, "This is a foo foo list") == 0); + + assert(replaceLast(r3, to!T("won't find"), to!T("whatever")) is r3); + }(); + } +} + /++ Returns a new array that is $(D s) with $(D slice) replaced by $(D replacement[]). @@ -2253,6 +2500,7 @@ body unittest { + import std.algorithm : cmp; debug(std_array) printf("array.replaceSlice.unittest\n"); string s = "hello"; @@ -2268,24 +2516,12 @@ unittest Implements an output range that appends data to an array. This is recommended over $(D a ~= data) when appending many elements because it is more efficient. - -Example: ----- -auto app = appender!string(); -string b = "abcdefg"; -foreach (char c; b) app.put(c); -assert(app.data == "abcdefg"); - -int[] a = [ 1, 2 ]; -auto app2 = appender(a); -app2.put(3); -app2.put([ 4, 5, 6 ]); -assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]); ----- */ struct Appender(A) if (isDynamicArray!A) { + import core.memory; + private alias T = ElementEncodingType!A; private struct Data { @@ -2472,12 +2708,15 @@ if (isDynamicArray!A) * - std.utf.encode */ // must do some transcoding around here + import std.utf : encode; Unqual!T[T.sizeof == 1 ? 4 : 2] encoded; - auto len = std.utf.encode(encoded, item); + auto len = encode(encoded, item); put(encoded[0 .. len]); } else { + import std.conv : emplaceRef; + ensureAddable(1); immutable len = _data.arr.length; @@ -2543,6 +2782,7 @@ if (isDynamicArray!A) } else { + import std.conv : emplaceRef; foreach (ref it ; bigData[len .. newlen]) { emplaceRef!T(it, items.front); @@ -2611,6 +2851,7 @@ if (isDynamicArray!A) */ void shrinkTo(size_t newlength) @safe pure { + import std.exception : enforce; if (_data) { enforce(newlength <= _data.arr.length); @@ -2633,9 +2874,24 @@ if (isDynamicArray!A) } } +/// +unittest{ + auto app = appender!string(); + string b = "abcdefg"; + foreach (char c; b) + app.put(c); + assert(app.data == "abcdefg"); + + int[] a = [ 1, 2 ]; + auto app2 = appender(a); + app2.put(3); + app2.put([ 4, 5, 6 ]); + assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]); +} + unittest { - import std.string : format; + import std.format : format; auto app = appender!(int[])(); app.put(1); app.put(2); @@ -2650,6 +2906,8 @@ unittest //ret sugLen: A suggested growth. private size_t appenderNewCapacity(size_t TSizeOf)(size_t curLen, size_t reqLen) @safe pure nothrow { + import core.bitop : bsr; + import std.algorithm : max; if(curLen == 0) return max(reqLen,8); ulong mult = 100 + (1000UL) / (bsr(curLen * TSizeOf) + 1); @@ -2764,6 +3022,7 @@ Appender!(E[]) appender(A : E[], E)(auto ref A array) @safe pure nothrow unittest { + import std.exception; { auto app = appender!(char[])(); string b = "abcdefg"; @@ -2916,6 +3175,8 @@ Appender!(E[]) appender(A : E[], E)(auto ref A array) unittest { + import std.typecons; + import std.algorithm; //10690 [tuple(1)].filter!(t => true).array; // No error [tuple("A")].filter!(t => true).array; // error @@ -2923,6 +3184,7 @@ unittest unittest { + import std.range; //Coverage for put(Range) struct S1 { @@ -2995,6 +3257,7 @@ unittest unittest { + import std.algorithm : map; //10753 struct Foo { immutable dchar d; @@ -3042,6 +3305,8 @@ unittest unittest //Test large allocations (for GC.extend) { + import std.range; + import std.algorithm : equal; Appender!(char[]) app; app.reserve(1); //cover reserve on non-initialized foreach(_; 0 .. 100_000) @@ -3126,6 +3391,7 @@ RefAppender!(E[]) appender(A : E[]*, E)(A array) unittest { + import std.exception; { auto arr = new char[0]; auto app = appender(&arr); @@ -3199,226 +3465,3 @@ unittest assert(appS.data == "hellow"); assert(appA.data == "hellow"); } - -/* -A simple slice type only holding pointers to the beginning and the end -of an array. Experimental duplication of the built-in slice - do not -use yet. - */ -struct SimpleSlice(T) -{ - private T * _b, _e; - - this(U...)(U values) - { - _b = cast(T*) core.memory.GC.malloc(U.length * T.sizeof); - _e = _b + U.length; - foreach (i, Unused; U) _b[i] = values[i]; - } - - void opAssign(R)(R anotherSlice) - { - static if (is(typeof(*_b = anotherSlice))) - { - // assign all elements to a value - foreach (p; _b .. _e) - { - *p = anotherSlice; - } - } - else - { - // assign another slice to this - enforce(anotherSlice.length == length); - auto p = _b; - foreach (p; _b .. _e) - { - *p = anotherSlice.front; - anotherSlice.popFront(); - } - } - } - -/** - Range primitives. - */ - bool empty() const - { - assert(_b <= _e); - return _b == _e; - } - -/// Ditto - ref T front() - { - assert(!empty); - return *_b; - } - -/// Ditto - void popFront() - { - assert(!empty); - ++_b; - } - -/// Ditto - ref T back() - { - assert(!empty); - return _e[-1]; - } - -/// Ditto - void popBack() - { - assert(!empty); - --_e; - } - -/// Ditto - T opIndex(size_t n) - { - assert(n < length); - return _b[n]; - } - -/// Ditto - const(T) opIndex(size_t n) const - { - assert(n < length); - return _b[n]; - } - -/// Ditto - void opIndexAssign(T value, size_t n) - { - assert(n < length); - _b[n] = value; - } - -/// Ditto - SimpleSliceLvalue!T opSlice() - { - typeof(return) result = void; - result._b = _b; - result._e = _e; - return result; - } - -/// Ditto - SimpleSliceLvalue!T opSlice(size_t x, size_t y) - { - enforce(x <= y && y <= length); - typeof(return) result = { _b + x, _b + y }; - return result; - } - - @property - { - /// Returns the length of the slice. - size_t length() const - { - return _e - _b; - } - - /** - Sets the length of the slice. Newly added elements will be filled with - $(D T.init). - */ - void length(size_t newLength) - { - immutable oldLength = length; - _b = cast(T*) core.memory.GC.realloc(_b, newLength * T.sizeof); - _e = _b + newLength; - this[oldLength .. $] = T.init; - } - } - -/// Concatenation. - SimpleSlice opCat(R)(R another) - { - immutable newLen = length + another.length; - typeof(return) result = void; - result._b = cast(T*) - core.memory.GC.malloc(newLen * T.sizeof); - result._e = result._b + newLen; - result[0 .. this.length] = this; - result[this.length .. result.length] = another; - return result; - } - -/// Concatenation with rebinding. - void opCatAssign(R)(R another) - { - auto newThis = this ~ another; - move(newThis, this); - } -} - -// Support for mass assignment -struct SimpleSliceLvalue(T) -{ - private SimpleSlice!T _s; - alias _s this; - - void opAssign(R)(R anotherSlice) - { - static if (is(typeof(*_b = anotherSlice))) - { - // assign all elements to a value - foreach (p; _b .. _e) - { - *p = anotherSlice; - } - } - else - { - // assign another slice to this - enforce(anotherSlice.length == length); - auto p = _b; - foreach (p; _b .. _e) - { - *p = anotherSlice.front; - anotherSlice.popFront(); - } - } - } -} - -unittest -{ - // SimpleSlice!(int) s; - - // s = SimpleSlice!(int)(4, 5, 6); - // assert(equal(s, [4, 5, 6][])); - // assert(s.length == 3); - // assert(s[0] == 4); - // assert(s[1] == 5); - // assert(s[2] == 6); - - // assert(s[] == s); - // assert(s[0 .. s.length] == s); - // assert(equal(s[0 .. s.length - 1], [4, 5][])); - - // auto s1 = s ~ s[0 .. 1]; - // assert(equal(s1, [4, 5, 6, 4][])); - - // assert(s1[3] == 4); - // s1[3] = 42; - // assert(s1[3] == 42); - - // const s2 = s; - // assert(s2.length == 3); - // assert(!s2.empty); - // assert(s2[0] == s[0]); - - // s[0 .. 2] = 10; - // assert(equal(s, [10, 10, 6][])); - - // s ~= [ 5, 9 ][]; - // assert(equal(s, [10, 10, 6, 5, 9][])); - - // s.length = 7; - // assert(equal(s, [10, 10, 6, 5, 9, 0, 0][])); -} diff --git a/std/base64.d b/std/base64.d index 210b41f50b6..f4ff9a90b1b 100644 --- a/std/base64.d +++ b/std/base64.d @@ -44,11 +44,9 @@ module std.base64; import std.exception; // enforce -import std.range; // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength +import std.range.primitives; // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength import std.traits; // isArray -version(unittest) import std.algorithm, std.conv, std.file, std.stdio; - /** * The Base64 @@ -1422,6 +1420,11 @@ class Base64Exception : Exception unittest { + import std.algorithm : sort, equal; + import std.conv; + import std.file; + import std.stdio; + alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); // Test vectors from RFC 4648 @@ -1527,6 +1530,8 @@ unittest } { // with OutputRange + import std.array; + auto a = Appender!(char[])([]); auto b = Appender!(ubyte[])([]); @@ -1602,7 +1607,7 @@ unittest string decode_file = std.file.deleteme ~ "-testingDecoder"; std.file.write(decode_file, "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy"); - auto witness = tv.keys.sort; + auto witness = sort(tv.keys); auto f = File(decode_file); scope(exit) { @@ -1620,7 +1625,7 @@ unittest { // ForwardRange { - auto encoder = Base64.encoder(tv.values.sort); + auto encoder = Base64.encoder(sort(tv.values)); auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]; size_t i; @@ -1634,7 +1639,7 @@ unittest { auto decoder = Base64.decoder(["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]); - auto witness = tv.values.sort; + auto witness = sort(tv.values); size_t i; assert(decoder.front == witness[i++]); decoder.popFront(); diff --git a/std/bigint.d b/std/bigint.d index d8a37d060c7..c95c3fd8d14 100644 --- a/std/bigint.d +++ b/std/bigint.d @@ -13,7 +13,7 @@ * * For very large numbers, consider using the $(WEB gmplib.org, GMP library) instead. * - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Don Clugston * Source: $(PHOBOSSRC std/_bigint.d) */ @@ -38,42 +38,13 @@ private import std.traits; * assignment is cheap, but operations such as x++ will cause heap * allocation. (But note that for most bigint operations, heap allocation is * inevitable anyway). - - Example: ----------------------------------------------------- - BigInt a = "9588669891916142"; - BigInt b = "7452469135154800"; - auto c = a * b; - assert(c == BigInt("71459266416693160362545788781600")); - auto d = b * a; - assert(d == BigInt("71459266416693160362545788781600")); - assert(d == c); - d = c * BigInt("794628672112"); - assert(d == BigInt("56783581982794522489042432639320434378739200")); - auto e = c + d; - assert(e == BigInt("56783581982865981755459125799682980167520800")); - auto f = d + c; - assert(f == e); - auto g = f - c; - assert(g == d); - g = f - d; - assert(g == c); - e = 12345678; - g = c + e; - auto h = g / b; - auto i = g % b; - assert(h == a); - assert(i == e); - BigInt j = "-0x9A56_57f4_7B83_AB78"; - j ^^= 11; ----------------------------------------------------- * */ struct BigInt { private: - BigUint data; // BigInt adds signed arithmetic to BigUint. - bool sign = false; + BigUint data; // BigInt adds signed arithmetic to BigUint. + bool sign = false; public: /// Construct a BigInt from a decimal or hexadecimal string. /// The number must be in the form of a D decimal or hex literal: @@ -183,6 +154,8 @@ public: else { data = cast(ulong)BigUint.modInt(data, cast(uint)u); + if (data.isZero()) + sign = false; } // x%y always has the same sign as x. // This is not the same as mathematical mod. @@ -286,15 +259,36 @@ public: } // - int opBinary(string op, T : int)(T y) pure nothrow const + auto opBinary(string op, T)(T y) pure nothrow const if (op == "%" && isIntegral!T) { assert(y!=0); - uint u = absUnsign(y); - int rem = BigUint.modInt(data, u); - // x%y always has the same sign as x. - // This is not the same as mathematical mod. - return sign ? -rem : rem; + + // BigInt % long => long + // BigInt % ulong => BigInt + // BigInt % other_type => int + static if (is(Unqual!T == long) || is(Unqual!T == ulong)) + { + auto r = this % BigInt(y); + + static if (is(Unqual!T == long)) + { + return r.toLong(); + } + else + { + // return as-is to avoid overflow + return r; + } + } + else + { + uint u = absUnsign(y); + int rem = BigUint.modInt(data, u); + // x%y always has the same sign as x. + // This is not the same as mathematical mod. + return sign ? -rem : rem; + } } // Commutative operators @@ -426,7 +420,7 @@ public: } /// Returns the value of this BigInt as a long, /// or +- long.max if outside the representable range. - long toLong() pure nothrow const + long toLong() pure nothrow const @nogc { return (sign ? -1 : 1) * (data.ulongLength == 1 && (data.peekUlong(0) <= sign+cast(ulong)(long.max)) // 1+long.max = |long.min| @@ -457,7 +451,7 @@ public: /** Convert the BigInt to string, passing it to 'sink'. * - * $(TABLE The output format is controlled via formatString: + * $(TABLE The output format is controlled via formatString:, * $(TR $(TD "d") $(TD Decimal)) * $(TR $(TD "x") $(TD Hexadecimal, lower case)) * $(TR $(TD "X") $(TD Hexadecimal, upper case)) @@ -519,20 +513,7 @@ public: foreach (i; 0 .. difw) sink(" "); } -/+ -private: - /// Convert to a hexadecimal string, with an underscore every - /// 8 characters. - string toHex() - { - string buff = data.toHexString(1, '_'); - if (isNegative()) - buff[0] = '-'; - else - buff = buff[1..$]; - return buff; - } -+/ + // Implement toHash so that BigInt works properly as an AA key. size_t toHash() const @trusted nothrow { @@ -562,6 +543,45 @@ private: } } +/// +unittest +{ + BigInt a = "9588669891916142"; + BigInt b = "7452469135154800"; + auto c = a * b; + assert(c == BigInt("71459266416693160362545788781600")); + auto d = b * a; + assert(d == BigInt("71459266416693160362545788781600")); + assert(d == c); + d = c * BigInt("794628672112"); + assert(d == BigInt("56783581982794522489042432639320434378739200")); + auto e = c + d; + assert(e == BigInt("56783581982865981755459125799682980167520800")); + auto f = d + c; + assert(f == e); + auto g = f - c; + assert(g == d); + g = f - d; + assert(g == c); + e = 12345678; + g = c + e; + auto h = g / b; + auto i = g % b; + assert(h == a); + assert(i == e); + BigInt j = "-0x9A56_57f4_7B83_AB78"; + j ^^= 11; +} + +/** This function returns a $(D string) representation of a $(D BigInt). + +Params: + x = The $(D BigInt) to convert to a decimal $(D string). + +Returns: + A $(D string) that represents the $(D BigInt) as a decimal number. + +*/ string toDecimalString(const(BigInt) x) { string outbuff=""; @@ -570,6 +590,15 @@ string toDecimalString(const(BigInt) x) return outbuff; } +/** This function returns a $(D string) representation of a $(D BigInt). + +Params: + x = The $(D BigInt) to convert to a hexadecimal $(D string). + +Returns: + A $(D string) that represents the $(D BigInt) as a hexadecimal number. + +*/ string toHex(const(BigInt) x) { string outbuff=""; @@ -578,7 +607,16 @@ string toHex(const(BigInt) x) return outbuff; } -// Returns the absolute value of x converted to the corresponding unsigned type +/** Returns the absolute value of x converted to the corresponding unsigned +type. + +Params: + x = The integral value to return the absolute value of. + +Returns: + The absolute value of x. + +*/ Unsigned!T absUnsign(T)(T x) if (isIntegral!T) { static if (isSigned!T) @@ -1031,3 +1069,68 @@ unittest // 13391 x2 /= 123456789123456789UL; assert(x2 == 1); } + +unittest // 13963 +{ + BigInt x = 1; + import std.typetuple : TypeTuple; + foreach(Int; TypeTuple!(byte, ubyte, short, ushort, int, uint)) + { + assert(is(typeof(x % Int(1)) == int)); + } + assert(is(typeof(x % 1L) == long)); + assert(is(typeof(x % 1UL) == BigInt)); + + auto x1 = BigInt(8); + auto x2 = -BigInt(long.min) + 1; + + // long + assert(x1 % 2L == 0L); + assert(-x1 % 2L == 0L); + + assert(x1 % 3L == 2L); + assert(x1 % -3L == 2L); + assert(-x1 % 3L == -2L); + assert(-x1 % -3L == -2L); + + assert(x1 % 11L == 8L); + assert(x1 % -11L == 8L); + assert(-x1 % 11L == -8L); + assert(-x1 % -11L == -8L); + + // ulong + assert(x1 % 2UL == BigInt(0)); + assert(-x1 % 2UL == BigInt(0)); + + assert(x1 % 3UL == BigInt(2)); + assert(-x1 % 3UL == -BigInt(2)); + + assert(x1 % 11UL == BigInt(8)); + assert(-x1 % 11UL == -BigInt(8)); + + assert(x2 % ulong.max == x2); + assert(-x2 % ulong.max == -x2); +} + +unittest // 14124 +{ + auto x = BigInt(-3); + x %= 3; + assert(!x.isNegative()); + assert(x.isZero()); + + x = BigInt(-3); + x %= cast(ushort)3; + assert(!x.isNegative()); + assert(x.isZero()); + + x = BigInt(-3); + x %= 3L; + assert(!x.isNegative()); + assert(x.isZero()); + + x = BigInt(3); + x %= -3; + assert(!x.isNegative()); + assert(x.isZero()); +} diff --git a/std/bitmanip.d b/std/bitmanip.d index 526b2a5fa7b..952776e0f3c 100644 --- a/std/bitmanip.d +++ b/std/bitmanip.d @@ -8,12 +8,13 @@ Macros: WIKI = StdBitarray Copyright: Copyright Digital Mars 2007 - 2011. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei Alexandrescu), Jonathan M Davis, Alex Rønne Petersen, Damian Ziemba + Amaury SECHET Source: $(PHOBOSSRC std/_bitmanip.d) */ /* @@ -26,17 +27,13 @@ module std.bitmanip; //debug = bitarray; // uncomment to turn on debugging printf's -import core.bitop; -import std.format; -import std.range; -import std.string; +import std.range.primitives; import std.system; import std.traits; version(unittest) { import std.stdio; - import std.typetuple; } @@ -163,6 +160,50 @@ private template createFields(string store, size_t offset, Ts...) } } +private ulong getBitsForAlign(ulong a) +{ + ulong bits = 0; + while ((a & 0x01) == 0) + { + bits++; + a >>= 1; + } + + assert(a == 1, "alignment is not a power of 2"); + return bits; +} + +private template createReferenceAccessor(string store, T, ulong bits, string name) +{ + enum mask = (1UL << bits) - 1; + // getter + enum result = "@property "~T.stringof~" "~name~"() @trusted pure nothrow @nogc const { auto result = " + ~ "("~store~" & "~myToString(~mask)~");" + ~ " return cast("~T.stringof~") cast(void*) result;}\n" + // setter + ~"@property void "~name~"("~T.stringof~" v) @trusted pure nothrow @nogc { " + ~"assert(((cast(typeof("~store~")) cast(void*) v) & "~myToString(mask)~`) == 0, "Value not properly aligned for '`~name~`'"); ` + ~store~" = cast(typeof("~store~"))" + ~" (("~store~" & (cast(typeof("~store~")) "~myToString(mask)~"))" + ~" | ((cast(typeof("~store~")) cast(void*) v) & (cast(typeof("~store~")) "~myToString(~mask)~")));}\n"; +} + +private template sizeOfBitField(T...) +{ + static if(T.length < 2) + enum sizeOfBitField = 0; + else + enum sizeOfBitField = T[2] + sizeOfBitField!(T[3 .. $]); +} + +private template createTaggedReference(string store, T, ulong a, string name, Ts...) +{ + static assert(sizeOfBitField!Ts <= getBitsForAlign(a), "Fields must fit in the bits know to be zero because of alignment."); + enum result + = createReferenceAccessor!(store, T, sizeOfBitField!Ts, name).result + ~ createFields!(store, 0, Ts, size_t, "", T.sizeof * 8 - sizeOfBitField!Ts).result; +} + /** Allows creating bit fields inside $(D_PARAM struct)s and $(D_PARAM class)es. @@ -215,6 +256,72 @@ template bitfields(T...) enum { bitfields = createFields!(createStoreName!(T), 0, T).result } } +/** +This string mixin generator allows one to create tagged pointers inside $(D_PARAM struct)s and $(D_PARAM class)es. + +A tagged pointer uses the bits known to be zero in a normal pointer or class reference to store extra information. +For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero. +One can store a 2-bit integer there. + +The example above creates a tagged pointer in the struct A. The pointer is of type +$(D uint*) as specified by the first argument, and is named x, as specified by the second +argument. + +Following arguments works the same way as $(D bitfield)'s. The bitfield must fit into the +bits known to be zero because of the pointer alignement. +*/ + +template taggedPointer(T : T*, string name, Ts...) { + enum taggedPointer = createTaggedReference!(createStoreName!(T, name, 0, Ts), T*, T.alignof, name, Ts).result; +} + +/// +unittest +{ + struct A + { + int a; + mixin(taggedPointer!( + uint*, "x", + bool, "b1", 1, + bool, "b2", 1)); + } + A obj; + obj.x = new uint; + obj.b1 = true; + obj.b2 = false; +} + +/** +This string mixin generator allows one to create tagged class reference inside $(D_PARAM struct)s and $(D_PARAM class)es. + +A tagged class reference uses the bits known to be zero in a normal class reference to store extra information. +For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero. +One can store a 2-bit integer there. + +The example above creates a tagged reference to an Object in the struct A. This expects the same parameters +as $(D taggedPointer), except the first argument which must be a class type instead of a pointer type. +*/ + +template taggedClassRef(T, string name, Ts...) if(is(T == class)) { + enum taggedClassRef = createTaggedReference!(createStoreName!(T, name, 0, Ts), T, 8, name, Ts).result; +} + +/// +unittest +{ + struct A + { + int a; + mixin(taggedClassRef!( + Object, "o", + uint, "i", 2)); + } + A obj; + obj.o = new Object(); + obj.i = 3; +} + @safe pure nothrow @nogc unittest { @@ -282,6 +389,59 @@ unittest t4b.a = -5; assert(t4b.a == -5L); } +unittest +{ + struct Test5 + { + mixin(taggedPointer!( + int*, "a", + uint, "b", 2)); + } + + Test5 t5; + t5.a = null; + t5.b = 3; + assert(t5.a is null); + assert(t5.b == 3); + + int myint = 42; + t5.a = &myint; + assert(t5.a is &myint); + assert(t5.b == 3); + + struct Test6 + { + mixin(taggedClassRef!( + Object, "o", + bool, "b", 1)); + } + + Test6 t6; + t6.o = null; + t6.b = false; + assert(t6.o is null); + assert(t6.b == false); + + auto o = new Object(); + t6.o = o; + t6.b = true; + assert(t6.o is o); + assert(t6.b == true); +} + +unittest +{ + static assert(!__traits(compiles, + taggedPointer!( + int*, "a", + uint, "b", 3))); + + static assert(!__traits(compiles, + taggedClassRef!( + Object, "a", + uint, "b", 4))); +} + unittest { // Bug #6686 @@ -541,6 +701,9 @@ unittest struct BitArray { + import std.format : FormatSpec; + import core.bitop: bts, btr, bsf, bt; + size_t len; size_t* ptr; enum bitsPerSizeT = size_t.sizeof * 8; @@ -754,7 +917,7 @@ public: static bool[] ba = [1,0,1]; - BitArray a; a.init(ba); + auto a = BitArray(ba); int i; foreach (b;a) @@ -817,7 +980,7 @@ public: static bool[5] data = [1,0,1,1,0]; int i; - b.init(data); + b = BitArray(data); b.reverse; for (i = 0; i < data.length; i++) { @@ -878,7 +1041,7 @@ public: debug(bitarray) printf("BitArray.sort.unittest\n"); __gshared size_t x = 0b1100011000; - __gshared BitArray ba = { 10, &x }; + __gshared ba = BitArray(10, &x); ba.sort; for (size_t i = 0; i < 6; i++) assert(ba[i] == false); @@ -919,13 +1082,13 @@ public: static bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; static bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]; - 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); - BitArray f; f.init(bf); - BitArray g; g.init(bg); + auto a = BitArray(ba); + auto b = BitArray(bb); + auto c = BitArray(bc); + auto d = BitArray(bd); + auto e = BitArray(be); + auto f = BitArray(bf); + auto g = BitArray(bg); assert(a != b); assert(a != c); @@ -985,13 +1148,13 @@ public: static bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]; static bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0]; - 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); - BitArray f; f.init(bf); - BitArray g; g.init(bg); + auto a = BitArray(ba); + auto b = BitArray(bb); + auto c = BitArray(bc); + auto d = BitArray(bd); + auto e = BitArray(be); + auto f = BitArray(bf); + auto g = BitArray(bg); assert(a > b); assert(a >= b); @@ -1010,9 +1173,9 @@ public: { v.length = i; v[] = false; - BitArray x; x.init(v); + auto x = BitArray(v); v[i-1] = true; - BitArray y; y.init(v); + auto y = BitArray(v); assert(x < y); assert(x <= y); } @@ -1055,10 +1218,26 @@ public: return hash; } + deprecated("Please use the constructor instead. This function will be removed in Jan 2016.") /*************************************** - * Set this $(D BitArray) to the contents of $(D ba). + * $(RED Will be deprecated in 2.068. Please use the constructor instead.) */ void init(bool[] ba) pure nothrow + { + this = BitArray(ba); + } + + deprecated("Please use the constructor instead. This function will be removed in Jan 2016.") + /// ditto + void init(void[] v, size_t numbits) pure nothrow + { + this = BitArray(v, numbits); + } + + /*************************************** + * Set this $(D BitArray) to the contents of $(D ba). + */ + this(bool[] ba) pure nothrow { length = ba.length; foreach (i, b; ba) @@ -1067,6 +1246,12 @@ public: } } + // Deliberately undocumented: raw initialization of bit array. + this(size_t _len, size_t* _ptr) + { + len = _len; + ptr = _ptr; + } /*************************************** * Map the $(D BitArray) onto $(D v), with $(D numbits) being the number of bits @@ -1075,8 +1260,9 @@ public: * these will be set to 0. * * This is the inverse of $(D opCast). + * $(RED Will be deprecated in 2.068. Please use the constructor instead.) */ - void init(void[] v, size_t numbits) pure nothrow + this(void[] v, size_t numbits) pure nothrow in { assert(numbits <= v.length * 8); @@ -1099,12 +1285,11 @@ public: static bool[] ba = [1,0,1,0,1]; - BitArray a; a.init(ba); - BitArray b; + auto a = BitArray(ba); void[] v; v = cast(void[])a; - b.init(v, a.length); + auto b = BitArray(v, a.length); assert(b[0] == 1); assert(b[1] == 0); @@ -1140,7 +1325,7 @@ public: static bool[] ba = [1,0,1,0,1]; - BitArray a; a.init(ba); + auto a = BitArray(ba); void[] v = cast(void[])a; assert(v.length == a.dim * size_t.sizeof); @@ -1172,7 +1357,7 @@ public: static bool[] ba = [1,0,1,0,1]; - BitArray a; a.init(ba); + auto a = BitArray(ba); BitArray b = ~a; assert(b[0] == 0); @@ -1219,8 +1404,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); BitArray c = a & b; @@ -1238,8 +1423,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); BitArray c = a | b; @@ -1257,8 +1442,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); BitArray c = a ^ b; @@ -1276,8 +1461,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); BitArray c = a - b; @@ -1325,8 +1510,8 @@ public: { static bool[] ba = [1,0,1,0,1,1,0,1,0,1]; static bool[] bb = [1,0,1,1,0]; - BitArray a; a.init(ba); - BitArray b; b.init(bb); + auto a = BitArray(ba); + auto b = BitArray(bb); BitArray c = a; c.length = 5; c &= b; @@ -1344,8 +1529,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); a &= b; assert(a[0] == 1); @@ -1362,8 +1547,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); a |= b; assert(a[0] == 1); @@ -1380,8 +1565,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); a ^= b; assert(a[0] == 0); @@ -1398,8 +1583,8 @@ public: 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); + auto a = BitArray(ba); + auto b = BitArray(bb); a -= b; assert(a[0] == 0); @@ -1430,7 +1615,7 @@ public: static bool[] ba = [1,0,1,0,1]; - BitArray a; a.init(ba); + auto a = BitArray(ba); BitArray b; b = (a ~= true); @@ -1464,8 +1649,8 @@ public: static bool[] ba = [1,0]; static bool[] bb = [0,1,0]; - BitArray a; a.init(ba); - BitArray b; b.init(bb); + auto a = BitArray(ba); + auto b = BitArray(bb); BitArray c; c = (a ~= b); @@ -1521,8 +1706,8 @@ public: static bool[] ba = [1,0]; static bool[] bb = [0,1,0]; - BitArray a; a.init(ba); - BitArray b; b.init(bb); + auto a = BitArray(ba); + auto b = BitArray(bb); BitArray c; c = (a ~ b); @@ -1546,6 +1731,232 @@ public: assert(c[2] == 0); } + // Rolls double word (upper, lower) to the right by n bits and returns the + // lower word of the result. + private static size_t rollRight()(size_t upper, size_t lower, size_t nbits) + pure @safe nothrow @nogc + in + { + assert(nbits < bitsPerSizeT); + } + body + { + return (upper << (bitsPerSizeT - nbits)) | (lower >> nbits); + } + + unittest + { + static if (size_t.sizeof == 8) + { + size_t x = 0x12345678_90ABCDEF; + size_t y = 0xFEDBCA09_87654321; + + assert(rollRight(x, y, 32) == 0x90ABCDEF_FEDBCA09); + assert(rollRight(y, x, 4) == 0x11234567_890ABCDE); + } + else static if (size_t.sizeof == 4) + { + size_t x = 0x12345678; + size_t y = 0x90ABCDEF; + + assert(rollRight(x, y, 16) == 0x567890AB); + assert(rollRight(y, x, 4) == 0xF1234567); + } + else + static assert(0, "Unsupported size_t width"); + } + + // Rolls double word (upper, lower) to the left by n bits and returns the + // upper word of the result. + private static size_t rollLeft()(size_t upper, size_t lower, size_t nbits) + pure @safe nothrow @nogc + in + { + assert(nbits < bitsPerSizeT); + } + body + { + return (upper << nbits) | (lower >> (bitsPerSizeT - nbits)); + } + + unittest + { + static if (size_t.sizeof == 8) + { + size_t x = 0x12345678_90ABCDEF; + size_t y = 0xFEDBCA09_87654321; + + assert(rollLeft(x, y, 32) == 0x90ABCDEF_FEDBCA09); + assert(rollLeft(y, x, 4) == 0xEDBCA098_76543211); + } + else static if (size_t.sizeof == 4) + { + size_t x = 0x12345678; + size_t y = 0x90ABCDEF; + + assert(rollLeft(x, y, 16) == 0x567890AB); + assert(rollLeft(y, x, 4) == 0x0ABCDEF1); + } + } + + /** + * Operator $(D <<=) support. + * + * Shifts all the bits in the array to the left by the given number of + * bits. The leftmost bits are dropped, and 0's are appended to the end + * to fill up the vacant bits. + * + * $(RED Warning: unused bits in the final word up to the next word + * boundary may be overwritten by this operation. It does not attempt to + * preserve bits past the end of the array.) + */ + void opOpAssign(string op)(size_t nbits) @nogc pure nothrow + if (op == "<<") + { + size_t wordsToShift = nbits / bitsPerSizeT; + size_t bitsToShift = nbits % bitsPerSizeT; + + if (wordsToShift < dim) + { + foreach_reverse (i; 1 .. dim - wordsToShift) + { + ptr[i + wordsToShift] = rollLeft(ptr[i], ptr[i-1], + bitsToShift); + } + ptr[wordsToShift] = rollLeft(ptr[0], 0, bitsToShift); + } + + import std.algorithm : min; + foreach (i; 0 .. min(wordsToShift, dim)) + { + ptr[i] = 0; + } + } + + /** + * Operator $(D >>=) support. + * + * Shifts all the bits in the array to the right by the given number of + * bits. The rightmost bits are dropped, and 0's are inserted at the back + * to fill up the vacant bits. + * + * $(RED Warning: unused bits in the final word up to the next word + * boundary may be overwritten by this operation. It does not attempt to + * preserve bits past the end of the array.) + */ + void opOpAssign(string op)(size_t nbits) @nogc pure nothrow + if (op == ">>") + { + size_t wordsToShift = nbits / bitsPerSizeT; + size_t bitsToShift = nbits % bitsPerSizeT; + + if (wordsToShift + 1 < dim) + { + foreach (i; 0 .. dim - wordsToShift - 1) + { + ptr[i] = rollRight(ptr[i + wordsToShift + 1], + ptr[i + wordsToShift], bitsToShift); + } + } + + // The last word needs some care, as it must shift in 0's from past the + // end of the array. + if (wordsToShift < dim) + { + ptr[dim - wordsToShift - 1] = rollRight(0, ptr[dim - 1] & endMask, + bitsToShift); + } + + import std.algorithm : min; + foreach (i; 0 .. min(wordsToShift, dim)) + { + ptr[dim - i - 1] = 0; + } + } + + unittest + { + import std.format : format; + + auto b = BitArray([1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1]); + + b <<= 1; + assert(format("%b", b) == "01100_10101101"); + + b >>= 1; + assert(format("%b", b) == "11001_01011010"); + + b <<= 4; + assert(format("%b", b) == "00001_10010101"); + + b >>= 5; + assert(format("%b", b) == "10010_10100000"); + + b <<= 13; + assert(format("%b", b) == "00000_00000000"); + + b = BitArray([1, 0, 1, 1, 0, 1, 1, 1]); + b >>= 8; + assert(format("%b", b) == "00000000"); + + } + + // Test multi-word case + unittest + { + import std.format : format; + + // This has to be long enough to occupy more than one size_t. On 64-bit + // machines, this would be at least 64 bits. + auto b = BitArray([ + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, + ]); + b <<= 8; + assert(format("%b", b) == + "00000000_10000000_"~ + "11000000_11100000_"~ + "11110000_11111000_"~ + "11111100_11111110_"~ + "11111111_10101010"); + + // Test right shift of more than one size_t's worth of bits + b <<= 68; + assert(format("%b", b) == + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00001000"); + + b = BitArray([ + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, + ]); + b >>= 8; + assert(format("%b", b) == + "11000000_11100000_"~ + "11110000_11111000_"~ + "11111100_11111110_"~ + "11111111_10101010_"~ + "01010101_00000000"); + + // Test left shift of more than 1 size_t's worth of bits + b >>= 68; + assert(format("%b", b) == + "01010000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000"); + } + /*************************************** * Return a string representation of this BitArray. * @@ -1571,9 +1982,10 @@ public: /// unittest { + import std.format : format; + debug(bitarray) printf("BitArray.toString unittest\n"); - BitArray b; - b.init([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + auto b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); auto s1 = format("%s", b); assert(s1 == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]"); @@ -1588,6 +2000,7 @@ public: @property auto bitsSet() const nothrow { import std.algorithm : filter, map, joiner; + import std.range : iota; return iota(dim). filter!(i => ptr[i])(). @@ -1600,8 +2013,7 @@ public: { import std.algorithm : equal; - BitArray b1; - b1.init([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + auto b1 = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); assert(b1.bitsSet.equal([4, 5, 6, 7, 12, 13, 14, 15])); BitArray b2; @@ -1615,21 +2027,22 @@ public: unittest { import std.algorithm : equal; + import std.range : iota; debug(bitarray) printf("BitArray.bitsSet unittest\n"); BitArray b; enum wordBits = size_t.sizeof * 8; - b.init([size_t.max], 0); + b = BitArray([size_t.max], 0); assert(b.bitsSet.empty); - b.init([size_t.max], 1); + b = BitArray([size_t.max], 1); assert(b.bitsSet.equal([0])); - b.init([size_t.max], wordBits); + b = BitArray([size_t.max], wordBits); assert(b.bitsSet.equal(iota(wordBits))); - b.init([size_t.max, size_t.max], wordBits); + b = BitArray([size_t.max, size_t.max], wordBits); assert(b.bitsSet.equal(iota(wordBits))); - b.init([size_t.max, size_t.max], wordBits + 1); + b = BitArray([size_t.max, size_t.max], wordBits + 1); assert(b.bitsSet.equal(iota(wordBits + 1))); - b.init([size_t.max, size_t.max], wordBits * 2); + b = BitArray([size_t.max, size_t.max], wordBits * 2); assert(b.bitsSet.equal(iota(wordBits * 2))); } @@ -1677,31 +2090,33 @@ public: unittest { + import std.format : format; + BitArray b; - b.init([]); + b = BitArray([]); assert(format("%s", b) == "[]"); assert(format("%b", b) is null); - b.init([1]); + b = BitArray([1]); assert(format("%s", b) == "[1]"); assert(format("%b", b) == "1"); - b.init([0, 0, 0, 0]); + b = BitArray([0, 0, 0, 0]); assert(format("%b", b) == "0000"); - b.init([0, 0, 0, 0, 1, 1, 1, 1]); + b = BitArray([0, 0, 0, 0, 1, 1, 1, 1]); assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1]"); assert(format("%b", b) == "00001111"); - b.init([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]"); assert(format("%b", b) == "00001111_00001111"); - b.init([1, 0, 0, 0, 0, 1, 1, 1, 1]); + b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1]); assert(format("%b", b) == "1_00001111"); - b.init([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); assert(format("%b", b) == "1_00001111_00001111"); } @@ -1733,17 +2148,20 @@ private ushort swapEndianImpl(ushort val) @safe pure nothrow @nogc private uint swapEndianImpl(uint val) @trusted pure nothrow @nogc { + import core.bitop: bswap; return bswap(val); } private ulong swapEndianImpl(ulong val) @trusted pure nothrow @nogc { + import core.bitop: bswap; immutable ulong res = bswap(cast(uint)val); return res << 32 | bswap(cast(uint)(val >> 32)); } unittest { + import std.typetuple; foreach(T; TypeTuple!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar)) { scope(failure) writefln("Failed type: %s", T.stringof); @@ -1816,17 +2234,6 @@ private union EndianSwapper(T) $(D real) is not supported, because its size is implementation-dependent and therefore could vary from machine to machine (which could make it unusable if you tried to transfer it to another machine). - - Examples: --------------------- -int i = 12345; -ubyte[4] swappedI = nativeToBigEndian(i); -assert(i == bigEndianToNative!int(swappedI)); - -double d = 123.45; -ubyte[8] swappedD = nativeToBigEndian(d); -assert(d == bigEndianToNative!double(swappedD)); --------------------- +/ auto nativeToBigEndian(T)(T val) @safe pure nothrow @nogc if(canSwapEndianness!T) @@ -1834,7 +2241,7 @@ auto nativeToBigEndian(T)(T val) @safe pure nothrow @nogc return nativeToBigEndianImpl(val); } -//Verify Examples +/// unittest { int i = 12345; @@ -1870,6 +2277,7 @@ private auto nativeToBigEndianImpl(T)(T val) @safe pure nothrow @nogc unittest { + import std.typetuple; foreach(T; TypeTuple!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar /* The trouble here is with floats and doubles being compared against nan @@ -1948,17 +2356,6 @@ unittest as a regular one (and in the case of floating point values, it's necessary, because the FPU will mess up any swapped floating point values. So, you can't actually have swapped floating point values as floating point values). - - Examples: --------------------- -ushort i = 12345; -ubyte[2] swappedI = nativeToBigEndian(i); -assert(i == bigEndianToNative!ushort(swappedI)); - -dchar c = 'D'; -ubyte[4] swappedC = nativeToBigEndian(c); -assert(c == bigEndianToNative!dchar(swappedC)); --------------------- +/ T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc if(canSwapEndianness!T && n == T.sizeof) @@ -1966,7 +2363,7 @@ T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc return bigEndianToNativeImpl!(T, n)(val); } -//Verify Examples. +/// unittest { ushort i = 12345; @@ -2011,17 +2408,6 @@ private T bigEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @n as a regular one (and in the case of floating point values, it's necessary, because the FPU will mess up any swapped floating point values. So, you can't actually have swapped floating point values as floating point values). - - Examples: --------------------- -int i = 12345; -ubyte[4] swappedI = nativeToLittleEndian(i); -assert(i == littleEndianToNative!int(swappedI)); - -double d = 123.45; -ubyte[8] swappedD = nativeToLittleEndian(d); -assert(d == littleEndianToNative!double(swappedD)); --------------------- +/ auto nativeToLittleEndian(T)(T val) @safe pure nothrow @nogc if(canSwapEndianness!T) @@ -2029,7 +2415,7 @@ auto nativeToLittleEndian(T)(T val) @safe pure nothrow @nogc return nativeToLittleEndianImpl(val); } -//Verify Examples. +/// unittest { int i = 12345; @@ -2065,6 +2451,7 @@ private auto nativeToLittleEndianImpl(T)(T val) @safe pure nothrow @nogc unittest { + import std.typetuple; foreach(T; TypeTuple!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar/*, float, double*/)) @@ -2116,17 +2503,6 @@ unittest $(D real) is not supported, because its size is implementation-dependent and therefore could vary from machine to machine (which could make it unusable if you tried to transfer it to another machine). - - Examples: --------------------- -ushort i = 12345; -ubyte[2] swappedI = nativeToLittleEndian(i); -assert(i == littleEndianToNative!ushort(swappedI)); - -dchar c = 'D'; -ubyte[4] swappedC = nativeToLittleEndian(c); -assert(c == littleEndianToNative!dchar(swappedC)); --------------------- +/ T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc if(canSwapEndianness!T && n == T.sizeof) @@ -2134,7 +2510,7 @@ T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc return littleEndianToNativeImpl!T(val); } -//Verify Unittest. +/// unittest { ushort i = 12345; @@ -2205,6 +2581,7 @@ private template isFloatOrDouble(T) unittest { + import std.typetuple; foreach(T; TypeTuple!(float, double)) { static assert(isFloatOrDouble!(T)); @@ -2233,6 +2610,7 @@ private template canSwapEndianness(T) unittest { + import std.typetuple; foreach(T; TypeTuple!(bool, ubyte, byte, ushort, short, uint, int, ulong, long, char, wchar, dchar, float, double)) { @@ -2269,28 +2647,6 @@ unittest front). If index is a pointer, then it is updated to the index after the bytes read. The overloads with index are only available if $(D hasSlicing!R) is $(D true). - - Examples: --------------------- -ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8]; -assert(buffer.peek!uint() == 17110537); -assert(buffer.peek!ushort() == 261); -assert(buffer.peek!ubyte() == 1); - -assert(buffer.peek!uint(2) == 369700095); -assert(buffer.peek!ushort(2) == 5641); -assert(buffer.peek!ubyte(2) == 22); - -size_t index = 0; -assert(buffer.peek!ushort(&index) == 261); -assert(index == 2); - -assert(buffer.peek!uint(&index) == 369700095); -assert(index == 6); - -assert(buffer.peek!ubyte(&index) == 8); -assert(index == 7); --------------------- +/ T peek(T, Endian endianness = Endian.bigEndian, R)(R range) @@ -2349,7 +2705,7 @@ T peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t* index) return littleEndianToNative!T(bytes); } -//Verify Example. +/// unittest { ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8]; @@ -2594,21 +2950,6 @@ unittest T = The integral type to convert the first $(D T.sizeof) bytes to. endianness = The endianness that the bytes are assumed to be in. range = The range to read from. - - Examples: --------------------- -ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8]; -assert(buffer.length == 7); - -assert(buffer.read!ushort() == 261); -assert(buffer.length == 5); - -assert(buffer.read!uint() == 369700095); -assert(buffer.length == 1); - -assert(buffer.read!ubyte() == 8); -assert(buffer.empty); --------------------- +/ T read(T, Endian endianness = Endian.bigEndian, R)(ref R range) if(canSwapEndianness!T && isInputRange!R && is(ElementType!R : const ubyte)) @@ -2635,7 +2976,7 @@ T read(T, Endian endianness = Endian.bigEndian, R)(ref R range) return littleEndianToNative!T(bytes); } -//Verify Example. +/// unittest { ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8]; @@ -2853,49 +3194,6 @@ unittest range = The range to write to. index = The index to start writing to. If index is a pointer, then it is updated to the index after the bytes read. - - Examples: --------------------- -{ - ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; - buffer.write!uint(29110231u, 0); - assert(buffer == [1, 188, 47, 215, 0, 0, 0, 0]); - - buffer.write!ushort(927, 0); - assert(buffer == [3, 159, 47, 215, 0, 0, 0, 0]); - - buffer.write!ubyte(42, 0); - assert(buffer == [42, 159, 47, 215, 0, 0, 0, 0]); -} - -{ - ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - buffer.write!uint(142700095u, 2); - assert(buffer == [0, 0, 8, 129, 110, 63, 0, 0, 0]); - - buffer.write!ushort(19839, 2); - assert(buffer == [0, 0, 77, 127, 110, 63, 0, 0, 0]); - - buffer.write!ubyte(132, 2); - assert(buffer == [0, 0, 132, 127, 110, 63, 0, 0, 0]); -} - -{ - ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; - size_t index = 0; - buffer.write!ushort(261, &index); - assert(buffer == [1, 5, 0, 0, 0, 0, 0, 0]); - assert(index == 2); - - buffer.write!uint(369700095u, &index); - assert(buffer == [1, 5, 22, 9, 44, 255, 0, 0]); - assert(index == 6); - - buffer.write!ubyte(8, &index); - assert(buffer == [1, 5, 22, 9, 44, 255, 8, 0]); - assert(index == 7); -} --------------------- +/ void write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t index) if(canSwapEndianness!T && @@ -2926,7 +3224,7 @@ void write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t* range[begin .. end] = bytes[0 .. T.sizeof]; } -//Verify Example. +/// unittest { { @@ -3239,19 +3537,6 @@ unittest T = The integral type to convert the first $(D T.sizeof) bytes to. endianness = The endianness to write the bytes in. range = The range to append to. - - Examples: --------------------- -auto buffer = appender!(const ubyte[])(); -buffer.append!ushort(261); -assert(buffer.data == [1, 5]); - -buffer.append!uint(369700095u); -assert(buffer.data == [1, 5, 22, 9, 44, 255]); - -buffer.append!ubyte(8); -assert(buffer.data == [1, 5, 22, 9, 44, 255, 8]); --------------------- +/ void append(T, Endian endianness = Endian.bigEndian, R)(R range, T value) if(canSwapEndianness!T && isOutputRange!(R, ubyte)) @@ -3264,9 +3549,10 @@ void append(T, Endian endianness = Endian.bigEndian, R)(R range, T value) put(range, bytes[]); } -//Verify Example. +/// unittest { + import std.array; auto buffer = appender!(const ubyte[])(); buffer.append!ushort(261); assert(buffer.data == [1, 5]); @@ -3280,6 +3566,7 @@ unittest unittest { + import std.array; { //bool auto buffer = appender!(const ubyte[])(); @@ -3410,8 +3697,9 @@ unittest unittest { - import std.string; - + import std.format : format; + import std.array; + import std.typetuple; foreach(endianness; TypeTuple!(Endian.bigEndian, Endian.littleEndian)) { auto toWrite = appender!(ubyte[])(); @@ -3454,6 +3742,7 @@ For signed integers, the sign bit is included in the count. private uint countTrailingZeros(T)(T value) @nogc pure nothrow if (isIntegral!T) { + import core.bitop : bsf; // bsf doesn't give the correct result for 0. if (!value) return 8 * T.sizeof; @@ -3485,6 +3774,7 @@ unittest unittest { + import std.typetuple; foreach (T; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong)) { assert(countTrailingZeros(cast(T)0) == 8 * T.sizeof); @@ -3565,6 +3855,7 @@ unittest unittest { + import std.typetuple; foreach (T; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong)) { assert(countBitsSet(cast(T)0) == 0); @@ -3652,6 +3943,7 @@ auto bitsSet(T)(T value) @nogc pure nothrow unittest { import std.algorithm : equal; + import std.range : iota; assert(bitsSet(1).equal([0])); assert(bitsSet(5).equal([0, 2])); @@ -3662,7 +3954,9 @@ unittest unittest { import std.algorithm : equal; + import std.range: iota; + import std.typetuple; foreach (T; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong)) { assert(bitsSet(cast(T)0).empty); diff --git a/std/c/fenv.d b/std/c/fenv.d index 02344b5fd70..d574a726316 100644 --- a/std/c/fenv.d +++ b/std/c/fenv.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.fenv) instead. This module will + * be removed in December 2015.) * C's <fenv.h> * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com * License: Public Domain @@ -7,6 +9,7 @@ * WIKI=Phobos/StdCFenv */ +/// Please import core.stdc.fenv instead. This module will be deprecated in DMD 2.068. module std.c.fenv; public import core.stdc.fenv; diff --git a/std/c/freebsd/socket.d b/std/c/freebsd/socket.d index c75dfd23cc6..0b19b2e046d 100644 --- a/std/c/freebsd/socket.d +++ b/std/c/freebsd/socket.d @@ -4,41 +4,12 @@ * This module is just for making std.socket work under FreeBSD, and these * definitions should actually be in druntime. (core.sys.posix.netdb or sth) */ +/// Please import the core.sys.posix.* modules you need instead. This module will be deprecated in DMD 2.068. module std.c.freebsd.socket; +version (FreeBSD): public import core.sys.posix.netdb; -import core.sys.posix.sys.socket; - -extern(C): - -enum // __BSD_VISIBLE -{ - AF_APPLETALK = 16, - AF_IPX = 23, -} - -enum // __BSD_VISIBLE -{ - SOCK_RDM = 4, -} - -enum // __BSD_VISIBLE -{ - MSG_NOSIGNAL = 0x20000, -} - -enum // __BSD_VISIBLE -{ - IPPROTO_IGMP = 2, - IPPROTO_GGP = 3, - IPPROTO_PUP = 12, - IPPROTO_IDP = 22, - IPPROTO_ND = 77, - IPPROTO_MAX = 256, -} - -enum // -{ - INADDR_LOOPBACK = 0x7f000001, - INADDR_NONE = 0xffffffff, -} +public import core.sys.posix.sys.socket : AF_APPLETALK, AF_IPX, SOCK_RDM, MSG_NOSIGNAL; +public import core.sys.posix.netinet.in_ : IPPROTO_IGMP, IPPROTO_GGP, + IPPROTO_PUP, IPPROTO_IDP, IPPROTO_ND, + IPPROTO_MAX, INADDR_LOOPBACK, INADDR_NONE; diff --git a/std/c/linux/linux.d b/std/c/linux/linux.d index 0252f470027..8e21afc093b 100644 --- a/std/c/linux/linux.d +++ b/std/c/linux/linux.d @@ -6,9 +6,11 @@ * countries. */ +/// Please import the core.sys.posix.* modules you need instead. This module will be deprecated in DMD 2.068. module std.c.linux.linux; -public import std.c.linux.pthread; +version (linux): +public import core.sys.posix.pthread; extern (C) { @@ -17,7 +19,6 @@ extern (C) void* __libc_stack_end; int __data_start; int _end; - int timezone; void *_deh_beg; void *_deh_end; diff --git a/std/c/linux/linuxextern.d b/std/c/linux/linuxextern.d index 8976005cd78..fcf3b3ee722 100644 --- a/std/c/linux/linuxextern.d +++ b/std/c/linux/linuxextern.d @@ -10,6 +10,7 @@ * Put them separate so they'll be externed - do not link in linuxextern.o */ +/// Please remove this import. This module is empty and will be deprecated in DMD 2.068. module std.c.linux.linuxextern; // No longer needed since "extern" storage class diff --git a/std/c/linux/pthread.d b/std/c/linux/pthread.d index 959428d7bd9..f2976f0fc1a 100644 --- a/std/c/linux/pthread.d +++ b/std/c/linux/pthread.d @@ -3,10 +3,10 @@ * Placed into public domain. */ +/// Please import core.sys.posix.pthread or the other core.sys.posix.* modules you need instead. This module will be deprecated in DMD 2.068. module std.c.linux.pthread; +version (linux): import std.c.linux.linux; -extern (C): - public import core.sys.posix.pthread; diff --git a/std/c/linux/socket.d b/std/c/linux/socket.d index 6d4e34784bd..9dba848708d 100644 --- a/std/c/linux/socket.d +++ b/std/c/linux/socket.d @@ -4,8 +4,10 @@ */ +/// Please import the core.sys.posix.* modules you need instead. This module will be deprecated in DMD 2.068. module std.c.linux.socket; +version (linux): private import core.stdc.stdint; public import core.sys.posix.arpa.inet; public import core.sys.posix.netdb; @@ -16,31 +18,6 @@ public import core.sys.posix.sys.socket; extern(C): -enum: int -{ - AF_IPX = 4, - AF_APPLETALK = 5, - PF_IPX = AF_IPX, - PF_APPLETALK = AF_APPLETALK, -} - -enum: int -{ - SOCK_RDM = 4, -} - -enum: int -{ - IPPROTO_IGMP = 2, - IPPROTO_GGP = 3, - IPPROTO_PUP = 12, - IPPROTO_IDP = 22, - IPPROTO_ND = 77, - IPPROTO_RAW = 255, - - IPPROTO_MAX = 256, -} - int gethostbyname_r(in char* name, hostent* ret, void* buf, size_t buflen, hostent** result, int* h_errnop); int gethostbyname2_r(in char* name, int af, hostent* ret, void* buf, size_t buflen, hostent** result, int* h_errnop); @@ -51,11 +28,6 @@ enum: int SD_BOTH = 2, } -enum: int -{ - MSG_NOSIGNAL = 0x4000, -} - enum: int { IP_MULTICAST_LOOP = 34, @@ -86,13 +58,6 @@ enum: int IPV6_XFRM_POLICY = 35, } -enum: uint -{ - INADDR_LOOPBACK = 0x7F000001, - INADDR_BROADCAST = 0xFFFFFFFF, - INADDR_NONE = 0xFFFFFFFF, -} - enum: int { TCP_NODELAY = 1, // Don't delay send to coalesce packets diff --git a/std/c/linux/termios.d b/std/c/linux/termios.d index 30e9443936e..bb5565751c1 100644 --- a/std/c/linux/termios.d +++ b/std/c/linux/termios.d @@ -1,7 +1,7 @@ +/// Please import core.sys.posix.termios instead. This module will be deprecated in DMD 2.068. module std.c.linux.termios; -extern (C): - +version (linux): public import core.sys.posix.termios; diff --git a/std/c/linux/tipc.d b/std/c/linux/tipc.d index b2e60e388f3..c17dec52f25 100644 --- a/std/c/linux/tipc.d +++ b/std/c/linux/tipc.d @@ -1,4 +1,6 @@ /** + * $(RED Deprecated. Please use $(D core.sys.linux.tipc) instead. This module + * will be removed in December 2015.) * Interface for Linux TIPC sockets, /usr/include/linux/tipc.h * * Copyright: Public Domain @@ -6,206 +8,7 @@ * Authors: Leandro Lucarella */ +/// Please import core.sys.linux.tipc instead. This module will be deprecated in DMD 2.068. module std.c.linux.tipc; -version (linux): - -extern (C): - -struct tipc_portid -{ - uint ref_; - uint node; -} - -struct tipc_name -{ - uint type; - uint instance; -} - -struct tipc_name_seq -{ - uint type; - uint lower; - uint upper; -} - -struct tipc_subscr -{ - tipc_name_seq seq; - uint timeout; - uint filter; - ubyte[8] usr_handle; -} - -struct tipc_event -{ - uint event; - uint found_lower; - uint found_upper; - tipc_portid port; - tipc_subscr s; -} - -struct sockaddr_tipc -{ - ushort family; - ubyte addrtype; - byte scope_; - union Addr - { - tipc_portid id; - tipc_name_seq nameseq; - static struct Name - { - tipc_name name; - uint domain; - } - Name name; - } - Addr addr; -} - -uint tipc_addr(uint zone, uint cluster, uint node) -{ - return (zone << 24) | (cluster << 12) | node; -} - -unittest -{ - assert (tipc_addr(0, 0, 0) == 0); - assert (tipc_addr(1, 1, 1) == 16781313); - assert (tipc_addr(2, 1, 27) == 33558555); - assert (tipc_addr(3, 1, 63) == 50335807); -} - -uint tipc_zone(uint addr) -{ - return addr >> 24; -} - -unittest -{ - assert (tipc_zone(0u) == 0); - assert (tipc_zone(16781313u) == 1); - assert (tipc_zone(33558555u) == 2); - assert (tipc_zone(50335807u) == 3); -} - -uint tipc_cluster(uint addr) -{ - return (addr >> 12) & 0xfff; -} - -unittest -{ - assert (tipc_cluster(0u) == 0); - assert (tipc_cluster(16781313u) == 1); - assert (tipc_cluster(33558555u) == 1); - assert (tipc_cluster(50335807u) == 1); -} - -uint tipc_node(uint addr) -{ - return addr & 0xfff; -} - -unittest -{ - assert (tipc_node(0u) == 0); - assert (tipc_node(16781313u) == 1); - assert (tipc_node(33558555u) == 27); - assert (tipc_node(50335807u) == 63); -} - -enum: int -{ - TIPC_CFG_SRV = 0, - TIPC_TOP_SRV = 1, - TIPC_RESERVED_TYPES = 64, -} - -enum: int -{ - TIPC_ZONE_SCOPE = 1, - TIPC_CLUSTER_SCOPE = 2, - TIPC_NODE_SCOPE = 3, -} - -enum: int -{ - TIPC_MAX_USER_MSG_SIZE = 66000, -} - -enum: int -{ - TIPC_LOW_IMPORTANCE = 0, - TIPC_MEDIUM_IMPORTANCE = 1, - TIPC_HIGH_IMPORTANCE = 2, - TIPC_CRITICAL_IMPORTANCE = 3, -} - -enum: int -{ - TIPC_OK = 0, - TIPC_ERR_NO_NAME = 1, - TIPC_ERR_NO_PORT = 2, - TIPC_ERR_NO_NODE = 3, - TIPC_ERR_OVERLOAD = 4, - TIPC_CONN_SHUTDOWN = 5, -} - -enum: int -{ - TIPC_SUB_PORTS = 0x01, - TIPC_SUB_SERVICE = 0x02, - TIPC_SUB_CANCEL = 0x04, -} - -version (none) enum: int -{ - TIPC_SUB_NO_BIND_EVTS = 0x04, - TIPC_SUB_NO_UNBIND_EVTS = 0x08, - TIPC_SUB_SINGLE_EVT = 0x10, -} - -enum: int -{ - TIPC_WAIT_FOREVER = ~0, -} - -enum: int -{ - - TIPC_PUBLISHED = 1, - TIPC_WITHDRAWN = 2, - TIPC_SUBSCR_TIMEOUT = 3, -} - -enum: int -{ - AF_TIPC = 30, - PF_TIPC = 30, - SOL_TIPC = 271, - TIPC_ADDR_NAMESEQ = 1, - TIPC_ADDR_MCAST = 1, - TIPC_ADDR_NAME = 2, - TIPC_ADDR_ID = 3, -} - -enum: int -{ - TIPC_ERRINFO = 1, - TIPC_RETDATA = 2, - TIPC_DESTNAME = 3, -} - -enum: int -{ - TIPC_IMPORTANCE = 127, - TIPC_SRC_DROPPABLE = 128, - TIPC_DEST_DROPPABLE = 129, - TIPC_CONN_TIMEOUT = 130, -} - +public import core.sys.linux.tipc; diff --git a/std/c/locale.d b/std/c/locale.d index 89d4004e8e5..b2d5eb207d8 100644 --- a/std/c/locale.d +++ b/std/c/locale.d @@ -1,4 +1,6 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.locale) instead. This module will + * be removed in December 2015.) * C's <locale.h> * License: Public Domain * Standards: @@ -6,6 +8,7 @@ * Macros: * WIKI=Phobos/StdCLocale */ +/// Please import core.stdc.locale instead. This module will be deprecated in DMD 2.068. module std.c.locale; public import core.stdc.locale; diff --git a/std/c/math.d b/std/c/math.d index cc6454a5848..d179237c6b1 100644 --- a/std/c/math.d +++ b/std/c/math.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.math) instead. This module will + * be removed in December 2015.) * C's <math.h> * Authors: Walter Bright, Digital Mars, www.digitalmars.com * License: Public Domain @@ -7,6 +9,7 @@ * WIKI=Phobos/StdCMath */ +/// Please import core.stdc.math instead. This module will be deprecated in DMD 2.068. module std.c.math; -public import core.stdc.math; \ No newline at end of file +public import core.stdc.math; diff --git a/std/c/osx/socket.d b/std/c/osx/socket.d index 2b1439fff25..262a196ccc7 100644 --- a/std/c/osx/socket.d +++ b/std/c/osx/socket.d @@ -4,8 +4,10 @@ */ +/// Please import the core.sys.posix.* modules you need instead. This module will be deprecated in DMD 2.068. module std.c.osx.socket; +version (OSX): private import core.stdc.stdint; public import core.sys.posix.arpa.inet; public import core.sys.posix.netdb; @@ -16,35 +18,7 @@ public import core.sys.posix.sys.socket; extern(C): -enum: int -{ - AF_IPX = 23, - AF_APPLETALK = 16, - PF_IPX = AF_IPX, - PF_APPLETALK = AF_APPLETALK, -} - -enum: int -{ - SOCK_RDM = 4, -} - -enum: int -{ - IPPROTO_IGMP = 2, - IPPROTO_GGP = 3, - IPPROTO_PUP = 12, - IPPROTO_IDP = 22, - IPPROTO_ND = 77, - IPPROTO_RAW = 255, - - IPPROTO_MAX = 256, -} - -int gethostbyname_r(in char* name, hostent* ret, void* buf, size_t buflen, hostent** result, int* h_errnop); -int gethostbyname2_r(in char* name, int af, hostent* ret, void* buf, size_t buflen, hostent** result, int* h_errnop); - -// Not defined in OSX, but we'll use them anyway +// Not defined in OSX, so these will be removed at the end of deprecation enum: int { SD_RECEIVE = 0, @@ -81,10 +55,3 @@ enum: int IPV6_IPSEC_POLICY = 28, //IPV6_XFRM_POLICY = 35, } - -enum: uint -{ - INADDR_LOOPBACK = 0x7F000001, - INADDR_BROADCAST = 0xFFFFFFFF, - INADDR_NONE = 0xFFFFFFFF, -} diff --git a/std/c/process.d b/std/c/process.d index 650b50426ef..6aafec6fc20 100644 --- a/std/c/process.d +++ b/std/c/process.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.stdlib) or the core.sys.posix.* + * modules you need instead. This module will be removed in December 2015.) * C's <process.h> * Authors: Walter Bright, Digital Mars, www.digitalmars.com * License: Public Domain @@ -7,39 +9,44 @@ * WIKI=Phobos/StdCProcess */ +/// Please import core.stdc.stdlib or the core.sys.posix.* modules you need instead. This module will be deprecated in DMD 2.068. module std.c.process; -private import std.c.stddef; +private import core.stdc.stddef; +public import core.stdc.stdlib : exit, abort, system; extern (C): -void exit(int); +//These declarations are not defined or used elsewhere. void _c_exit(); void _cexit(); -void _exit(int); -void abort(); void _dodtors(); int getpid(); +enum { WAIT_CHILD, WAIT_GRANDCHILD } +int cwait(int *,int,int); +int wait(int *); +int execlpe(in char *, in char *,...); -int system(in char *); - +//These constants are undefined elsewhere and only used in the deprecated part +//of std.process. enum { _P_WAIT, _P_NOWAIT, _P_OVERLAY }; +//These declarations are defined for Posix in core.sys.posix.unistd but unused +//from here. +void _exit(int); int execl(in char *, in char *,...); int execle(in char *, in char *,...); int execlp(in char *, in char *,...); -int execlpe(in char *, in char *,...); + +//All of these except for execvpe are defined for Posix in core.sys.posix.unistd +//and only used in the old part of std.process. int execv(in char *, in char **); int execve(in char *, in char **, in char **); int execvp(in char *, in char **); int execvpe(in char *, in char **, in char **); - -enum { WAIT_CHILD, WAIT_GRANDCHILD } - -int cwait(int *,int,int); -int wait(int *); - +//All these Windows declarations are not publicly defined elsewhere and only +//spawnvp is used once in a deprecated function in std.process. version (Windows) { uint _beginthread(void function(void *),uint,void *); diff --git a/std/c/stdarg.d b/std/c/stdarg.d index 8fd2d4915a3..e82a357abe0 100644 --- a/std/c/stdarg.d +++ b/std/c/stdarg.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.stdarg) instead. This module will + * be removed in December 2015.) * C's <stdarg.h> * Authors: Hauke Duden and Walter Bright, Digital Mars, www.digitalmars.com * License: Public Domain @@ -9,6 +11,7 @@ /* This is for use with extern(C) variable argument lists. */ +/// Please import core.stdc.stdarg instead. This module will be deprecated in DMD 2.068. module std.c.stdarg; public import core.stdc.stdarg; diff --git a/std/c/stddef.d b/std/c/stddef.d index 8f41d5d35bf..4eb74cff70c 100644 --- a/std/c/stddef.d +++ b/std/c/stddef.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.stddef) instead. This module will + * be removed in December 2015.) * C's <stddef.h> * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com * License: Public Domain @@ -7,6 +9,7 @@ * WIKI=Phobos/StdCStddef */ +/// Please import core.stdc.stddef instead. This module will be deprecated in DMD 2.068. module std.c.stddef; public import core.stdc.stddef; diff --git a/std/c/stdio.d b/std/c/stdio.d index 40ea1b22856..95fcdfefd97 100644 --- a/std/c/stdio.d +++ b/std/c/stdio.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.stdio) instead. This module will + * be removed in December 2015.) * C's <stdio.h> for the D programming language * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com * License: Public Domain @@ -9,13 +11,7 @@ +/// Please import core.stdc.stdio instead. This module will be deprecated in DMD 2.068. module std.c.stdio; public import core.stdc.stdio; - -extern (C): - -version (Windows) -{ - extern shared ubyte[_NFILE] __fhnd_info; -} diff --git a/std/c/stdlib.d b/std/c/stdlib.d index 3b34a9e174f..412d94f39e1 100644 --- a/std/c/stdlib.d +++ b/std/c/stdlib.d @@ -1,4 +1,6 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.stdlib) or $(D core.sys.posix.stdlib) + * instead. This module will be removed in December 2015.) * C's <stdlib.h> * D Programming Language runtime library * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com @@ -8,11 +10,8 @@ */ +/// Please import core.stdc.stdlib or core.sys.posix.stdlib instead. This module will be deprecated in DMD 2.068. module std.c.stdlib; public import core.stdc.stdlib; - -extern (C): - -int setenv(const char*, const char*, int); /// extension to ISO C standard, not available on all platforms -int unsetenv(const char*); /// extension to ISO C standard, not available on all platforms +version(Posix) public import core.sys.posix.stdlib: setenv, unsetenv; diff --git a/std/c/string.d b/std/c/string.d index 3a4e61ae2ff..cfd5c00771c 100644 --- a/std/c/string.d +++ b/std/c/string.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.string) instead. This module will + * be removed in December 2015.) * C's <string.h> * Authors: Walter Bright, Digital Mars, http://www.digitalmars.com * License: Public Domain @@ -7,7 +9,7 @@ * WIKI=Phobos/StdCString */ +/// Please import core.stdc.string instead. This module will be deprecated in DMD 2.068. module std.c.string; -deprecated("Please import core.stdc.string instead.") public import core.stdc.string; diff --git a/std/c/time.d b/std/c/time.d index f0fe47a62c9..24e5bc79064 100644 --- a/std/c/time.d +++ b/std/c/time.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.time) instead. This module will + * be removed in December 2015.) * C's <time.h> * Authors: Walter Bright, Digital Mars, www.digitalmars.com * License: Public Domain @@ -7,6 +9,7 @@ * WIKI=Phobos/StdCTime */ +/// Please import core.stdc.time instead. This module will be deprecated in DMD 2.068. module std.c.time; public import core.stdc.time; diff --git a/std/c/wcharh.d b/std/c/wcharh.d index f4e93cc42c7..561d4f10054 100644 --- a/std/c/wcharh.d +++ b/std/c/wcharh.d @@ -1,5 +1,7 @@ /** + * $(RED Deprecated. Please use $(D core.stdc.wchar_) instead. This module will + * be removed in December 2015.) * C's <wchar.h> * Authors: Walter Bright, Digital Mars, www.digitalmars.com * License: Public Domain @@ -7,6 +9,7 @@ * WIKI=Phobos/StdCWchar */ +/// Please import core.stdc.wchar_ instead. This module will be deprecated in DMD 2.068. module std.c.wcharh; public import core.stdc.wchar_; diff --git a/std/c/windows/com.d b/std/c/windows/com.d index f862e1025d0..cd0fb93a2cb 100644 --- a/std/c/windows/com.d +++ b/std/c/windows/com.d @@ -1,260 +1,5 @@ +/// Please import core.sys.windows.com instead. This module will be deprecated in DMD 2.068. module std.c.windows.com; -version (Windows): - -pragma(lib,"uuid"); - -import core.atomic; -import std.c.windows.windows; -import std.string; - -alias WCHAR OLECHAR; -alias LPOLESTR = OLECHAR*; -alias LPCOLESTR = OLECHAR*; - -enum -{ - rmm = 23, // OLE 2 version number info - rup = 639, -} - -enum : int -{ - S_OK = 0, - S_FALSE = 0x00000001, - NOERROR = 0, - E_NOTIMPL = cast(int)0x80004001, - E_NOINTERFACE = cast(int)0x80004002, - E_POINTER = cast(int)0x80004003, - E_ABORT = cast(int)0x80004004, - E_FAIL = cast(int)0x80004005, - E_HANDLE = cast(int)0x80070006, - CLASS_E_NOAGGREGATION = cast(int)0x80040110, - E_OUTOFMEMORY = cast(int)0x8007000E, - E_INVALIDARG = cast(int)0x80070057, - E_UNEXPECTED = cast(int)0x8000FFFF, -} - -struct GUID { // size is 16 - align(1): - DWORD Data1; - WORD Data2; - WORD Data3; - BYTE[8] Data4; -} - -enum -{ - CLSCTX_INPROC_SERVER = 0x1, - CLSCTX_INPROC_HANDLER = 0x2, - CLSCTX_LOCAL_SERVER = 0x4, - CLSCTX_INPROC_SERVER16 = 0x8, - CLSCTX_REMOTE_SERVER = 0x10, - CLSCTX_INPROC_HANDLER16 = 0x20, - CLSCTX_INPROC_SERVERX86 = 0x40, - CLSCTX_INPROC_HANDLERX86 = 0x80, - - CLSCTX_INPROC = (CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER), - CLSCTX_ALL = (CLSCTX_INPROC_SERVER| CLSCTX_INPROC_HANDLER| CLSCTX_LOCAL_SERVER), - CLSCTX_SERVER = (CLSCTX_INPROC_SERVER|CLSCTX_LOCAL_SERVER), -} - -enum -{ - COINIT_APARTMENTTHREADED = 0x2, - COINIT_MULTITHREADED = 0x0, - COINIT_DISABLE_OLE1DDE = 0x4, - COINIT_SPEED_OVER_MEMORY = 0x8 -} -alias COINIT = DWORD; -enum RPC_E_CHANGED_MODE = 0x80010106; - -alias IID = const(GUID); -alias CLSID = const(GUID); - -extern (C) -{ - extern IID IID_IUnknown; - extern IID IID_IClassFactory; - extern IID IID_IMarshal; - extern IID IID_IMallocSpy; - extern IID IID_IStdMarshalInfo; - extern IID IID_IExternalConnection; - extern IID IID_IMultiQI; - extern IID IID_IEnumUnknown; - extern IID IID_IBindCtx; - extern IID IID_IEnumMoniker; - extern IID IID_IRunnableObject; - extern IID IID_IRunningObjectTable; - extern IID IID_IPersist; - extern IID IID_IPersistStream; - extern IID IID_IMoniker; - extern IID IID_IROTData; - extern IID IID_IEnumString; - extern IID IID_ISequentialStream; - extern IID IID_IStream; - extern IID IID_IEnumSTATSTG; - extern IID IID_IStorage; - extern IID IID_IPersistFile; - extern IID IID_IPersistStorage; - extern IID IID_ILockBytes; - extern IID IID_IEnumFORMATETC; - extern IID IID_IEnumSTATDATA; - extern IID IID_IRootStorage; - extern IID IID_IAdviseSink; - extern IID IID_IAdviseSink2; - extern IID IID_IDataObject; - extern IID IID_IDataAdviseHolder; - extern IID IID_IMessageFilter; - extern IID IID_IRpcChannelBuffer; - extern IID IID_IRpcProxyBuffer; - extern IID IID_IRpcStubBuffer; - extern IID IID_IPSFactoryBuffer; - extern IID IID_IPropertyStorage; - extern IID IID_IPropertySetStorage; - extern IID IID_IEnumSTATPROPSTG; - extern IID IID_IEnumSTATPROPSETSTG; - extern IID IID_IFillLockBytes; - extern IID IID_IProgressNotify; - extern IID IID_ILayoutStorage; - extern IID GUID_NULL; - extern IID IID_IRpcChannel; - extern IID IID_IRpcStub; - extern IID IID_IStubManager; - extern IID IID_IRpcProxy; - extern IID IID_IProxyManager; - extern IID IID_IPSFactory; - extern IID IID_IInternalMoniker; - extern IID IID_IDfReserved1; - extern IID IID_IDfReserved2; - extern IID IID_IDfReserved3; - extern IID IID_IStub; - extern IID IID_IProxy; - extern IID IID_IEnumGeneric; - extern IID IID_IEnumHolder; - extern IID IID_IEnumCallback; - extern IID IID_IOleManager; - extern IID IID_IOlePresObj; - extern IID IID_IDebug; - extern IID IID_IDebugStream; - extern IID IID_StdOle; - extern IID IID_ICreateTypeInfo; - extern IID IID_ICreateTypeInfo2; - extern IID IID_ICreateTypeLib; - extern IID IID_ICreateTypeLib2; - extern IID IID_IDispatch; - extern IID IID_IEnumVARIANT; - extern IID IID_ITypeComp; - extern IID IID_ITypeInfo; - extern IID IID_ITypeInfo2; - extern IID IID_ITypeLib; - extern IID IID_ITypeLib2; - extern IID IID_ITypeChangeEvents; - extern IID IID_IErrorInfo; - extern IID IID_ICreateErrorInfo; - extern IID IID_ISupportErrorInfo; - extern IID IID_IOleAdviseHolder; - extern IID IID_IOleCache; - extern IID IID_IOleCache2; - extern IID IID_IOleCacheControl; - extern IID IID_IParseDisplayName; - extern IID IID_IOleContainer; - extern IID IID_IOleClientSite; - extern IID IID_IOleObject; - extern IID IID_IOleWindow; - extern IID IID_IOleLink; - extern IID IID_IOleItemContainer; - extern IID IID_IOleInPlaceUIWindow; - extern IID IID_IOleInPlaceActiveObject; - extern IID IID_IOleInPlaceFrame; - extern IID IID_IOleInPlaceObject; - extern IID IID_IOleInPlaceSite; - extern IID IID_IContinue; - extern IID IID_IViewObject; - extern IID IID_IViewObject2; - extern IID IID_IDropSource; - extern IID IID_IDropTarget; - extern IID IID_IEnumOLEVERB; -} - -extern (System) -{ - -export -{ -DWORD CoBuildVersion(); - -int StringFromGUID2(GUID *rguid, LPOLESTR lpsz, int cbMax); -/* init/uninit */ - -HRESULT CoInitialize(LPVOID pvReserved); -HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit); -void CoUninitialize(); -DWORD CoGetCurrentProcess(); - - -HRESULT CoCreateInstance(const(CLSID) *rclsid, IUnknown UnkOuter, - DWORD dwClsContext, const(IID)* riid, void* ppv); - -//HINSTANCE CoLoadLibrary(LPOLESTR lpszLibName, BOOL bAutoFree); -void CoFreeLibrary(HINSTANCE hInst); -void CoFreeAllLibraries(); -void CoFreeUnusedLibraries(); -} - -interface IUnknown -{ - HRESULT QueryInterface(const(IID)* riid, void** pvObject); - ULONG AddRef(); - ULONG Release(); -} - -interface IClassFactory : IUnknown -{ - HRESULT CreateInstance(IUnknown UnkOuter, IID* riid, void** pvObject); - HRESULT LockServer(BOOL fLock); -} - -class ComObject : IUnknown -{ -extern (System): - HRESULT QueryInterface(const(IID)* riid, void** ppv) - { - if (*riid == IID_IUnknown) - { - *ppv = cast(void*)cast(IUnknown)this; - AddRef(); - return S_OK; - } - else - { *ppv = null; - return E_NOINTERFACE; - } - } - - ULONG AddRef() - { - return atomicOp!"+="(*cast(shared)&count, 1); - } - - ULONG Release() - { - LONG lRef = atomicOp!"-="(*cast(shared)&count, 1); - if (lRef == 0) - { - // free object - - // If we delete this object, then the postinvariant called upon - // return from Release() will fail. - // Just let the GC reap it. - //delete this; - - return 0; - } - return cast(ULONG)lRef; - } - - LONG count = 0; // object reference count -} - -} +version (Windows): +public import core.sys.windows.com; diff --git a/std/c/windows/stat.d b/std/c/windows/stat.d index 8c2f84e50f8..018b2a07591 100644 --- a/std/c/windows/stat.d +++ b/std/c/windows/stat.d @@ -2,49 +2,8 @@ /// Placed into public domain /// Author: Walter Bright +/// Please import core.sys.windows.stat instead. This module will be deprecated in DMD 2.068. module std.c.windows.stat; -version (Windows): - -extern (C) nothrow @nogc: - -// linux version is in std.c.linux.linux - -const S_IFMT = 0xF000; -const S_IFDIR = 0x4000; -const S_IFCHR = 0x2000; -const S_IFIFO = 0x1000; -const S_IFREG = 0x8000; -const S_IREAD = 0x0100; -const S_IWRITE = 0x0080; -const S_IEXEC = 0x0040; -const S_IFBLK = 0x6000; -const S_IFNAM = 0x5000; -@safe pure -{ -int S_ISREG(int m) { return (m & S_IFMT) == S_IFREG; } -int S_ISBLK(int m) { return (m & S_IFMT) == S_IFBLK; } -int S_ISNAM(int m) { return (m & S_IFMT) == S_IFNAM; } -int S_ISDIR(int m) { return (m & S_IFMT) == S_IFDIR; } -int S_ISCHR(int m) { return (m & S_IFMT) == S_IFCHR; } -} - -struct struct_stat -{ - short st_dev; - ushort st_ino; - ushort st_mode; - short st_nlink; - ushort st_uid; - ushort st_gid; - short st_rdev; - short dummy; - int st_size; - int st_atime; - int st_mtime; - int st_ctime; -} - -int stat(char *, struct_stat *); -int fstat(int, struct_stat *); -int _wstat(wchar *, struct_stat *); +version (Windows): +public import core.sys.windows.stat; diff --git a/std/c/windows/windows.d b/std/c/windows/windows.d index 8e44ed07f8d..936c8377586 100644 --- a/std/c/windows/windows.d +++ b/std/c/windows/windows.d @@ -2,7 +2,8 @@ /* Windows is a registered trademark of Microsoft Corporation in the United States and other countries. */ +/// Please import core.sys.windows.windows instead. This module will be deprecated in DMD 2.068. module std.c.windows.windows; -version (Windows): +version (Windows): public import core.sys.windows.windows; diff --git a/std/c/windows/winsock.d b/std/c/windows/winsock.d index cdb1ede82e0..926fc2f4d97 100644 --- a/std/c/windows/winsock.d +++ b/std/c/windows/winsock.d @@ -4,732 +4,8 @@ */ +/// Please import core.sys.windows.winsock2 instead.This module will be deprecated in DMD 2.068. module std.c.windows.winsock; -version (Windows): - -extern(Windows): -nothrow: - -alias SOCKET = size_t; -alias socklen_t = int; - -const SOCKET INVALID_SOCKET = cast(SOCKET)~0; -const int SOCKET_ERROR = -1; - -enum WSADESCRIPTION_LEN = 256; -enum WSASYS_STATUS_LEN = 128; - -struct WSADATA -{ - ushort wVersion; - ushort wHighVersion; - char[WSADESCRIPTION_LEN + 1] szDescription; - char[WSASYS_STATUS_LEN + 1] szSystemStatus; - ushort iMaxSockets; - ushort iMaxUdpDg; - char* lpVendorInfo; -} -alias LPWSADATA = WSADATA*; - - -const int IOCPARM_MASK = 0x7F; -const int IOC_IN = cast(int)0x80000000; -const int FIONBIO = cast(int)(IOC_IN | ((uint.sizeof & IOCPARM_MASK) << 16) | (102 << 8) | 126); - -enum NI_MAXHOST = 1025; -enum NI_MAXSERV = 32; - -@nogc -{ -int WSAStartup(ushort wVersionRequested, LPWSADATA lpWSAData); -int WSACleanup(); -SOCKET socket(int af, int type, int protocol); -int ioctlsocket(SOCKET s, int cmd, uint* argp); -int bind(SOCKET s, const(sockaddr)* name, socklen_t namelen); -int connect(SOCKET s, const(sockaddr)* name, socklen_t namelen); -int listen(SOCKET s, int backlog); -SOCKET accept(SOCKET s, sockaddr* addr, socklen_t* addrlen); -int closesocket(SOCKET s); -int shutdown(SOCKET s, int how); -int getpeername(SOCKET s, sockaddr* name, socklen_t* namelen); -int getsockname(SOCKET s, sockaddr* name, socklen_t* namelen); -int send(SOCKET s, const(void)* buf, int len, int flags); -int sendto(SOCKET s, const(void)* buf, int len, int flags, const(sockaddr)* to, socklen_t tolen); -int recv(SOCKET s, void* buf, int len, int flags); -int recvfrom(SOCKET s, void* buf, int len, int flags, sockaddr* from, socklen_t* fromlen); -int getsockopt(SOCKET s, int level, int optname, void* optval, socklen_t* optlen); -int setsockopt(SOCKET s, int level, int optname, const(void)* optval, socklen_t optlen); -uint inet_addr(const char* cp); -int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, const(timeval)* timeout); -char* inet_ntoa(in_addr ina); -hostent* gethostbyname(const char* name); -hostent* gethostbyaddr(const(void)* addr, int len, int type); -protoent* getprotobyname(const char* name); -protoent* getprotobynumber(int number); -servent* getservbyname(const char* name, const char* proto); -servent* getservbyport(int port, const char* proto); -} - -enum: int -{ - NI_NOFQDN = 0x01, - NI_NUMERICHOST = 0x02, - NI_NAMEREQD = 0x04, - NI_NUMERICSERV = 0x08, - NI_DGRAM = 0x10, -} - -@nogc -{ -int gethostname(const char* name, int namelen); -int getaddrinfo(const(char)* nodename, const(char)* servname, const(addrinfo)* hints, addrinfo** res); -void freeaddrinfo(addrinfo* ai); -int getnameinfo(const(sockaddr)* sa, socklen_t salen, char* host, uint hostlen, char* serv, uint servlen, int flags); -} - -enum WSABASEERR = 10000; - -enum: int -{ - /* - * Windows Sockets definitions of regular Microsoft C error constants - */ - WSAEINTR = (WSABASEERR+4), - WSAEBADF = (WSABASEERR+9), - WSAEACCES = (WSABASEERR+13), - WSAEFAULT = (WSABASEERR+14), - WSAEINVAL = (WSABASEERR+22), - WSAEMFILE = (WSABASEERR+24), - - /* - * Windows Sockets definitions of regular Berkeley error constants - */ - WSAEWOULDBLOCK = (WSABASEERR+35), - WSAEINPROGRESS = (WSABASEERR+36), - WSAEALREADY = (WSABASEERR+37), - WSAENOTSOCK = (WSABASEERR+38), - WSAEDESTADDRREQ = (WSABASEERR+39), - WSAEMSGSIZE = (WSABASEERR+40), - WSAEPROTOTYPE = (WSABASEERR+41), - WSAENOPROTOOPT = (WSABASEERR+42), - WSAEPROTONOSUPPORT = (WSABASEERR+43), - WSAESOCKTNOSUPPORT = (WSABASEERR+44), - WSAEOPNOTSUPP = (WSABASEERR+45), - WSAEPFNOSUPPORT = (WSABASEERR+46), - WSAEAFNOSUPPORT = (WSABASEERR+47), - WSAEADDRINUSE = (WSABASEERR+48), - WSAEADDRNOTAVAIL = (WSABASEERR+49), - WSAENETDOWN = (WSABASEERR+50), - WSAENETUNREACH = (WSABASEERR+51), - WSAENETRESET = (WSABASEERR+52), - WSAECONNABORTED = (WSABASEERR+53), - WSAECONNRESET = (WSABASEERR+54), - WSAENOBUFS = (WSABASEERR+55), - WSAEISCONN = (WSABASEERR+56), - WSAENOTCONN = (WSABASEERR+57), - WSAESHUTDOWN = (WSABASEERR+58), - WSAETOOMANYREFS = (WSABASEERR+59), - WSAETIMEDOUT = (WSABASEERR+60), - WSAECONNREFUSED = (WSABASEERR+61), - WSAELOOP = (WSABASEERR+62), - WSAENAMETOOLONG = (WSABASEERR+63), - WSAEHOSTDOWN = (WSABASEERR+64), - WSAEHOSTUNREACH = (WSABASEERR+65), - WSAENOTEMPTY = (WSABASEERR+66), - WSAEPROCLIM = (WSABASEERR+67), - WSAEUSERS = (WSABASEERR+68), - WSAEDQUOT = (WSABASEERR+69), - WSAESTALE = (WSABASEERR+70), - WSAEREMOTE = (WSABASEERR+71), - - /* - * Extended Windows Sockets error constant definitions - */ - WSASYSNOTREADY = (WSABASEERR+91), - WSAVERNOTSUPPORTED = (WSABASEERR+92), - WSANOTINITIALISED = (WSABASEERR+93), - - /* Authoritative Answer: Host not found */ - WSAHOST_NOT_FOUND = (WSABASEERR+1001), - HOST_NOT_FOUND = WSAHOST_NOT_FOUND, - - /* Non-Authoritative: Host not found, or SERVERFAIL */ - WSATRY_AGAIN = (WSABASEERR+1002), - TRY_AGAIN = WSATRY_AGAIN, - - /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ - WSANO_RECOVERY = (WSABASEERR+1003), - NO_RECOVERY = WSANO_RECOVERY, - - /* Valid name, no data record of requested type */ - WSANO_DATA = (WSABASEERR+1004), - NO_DATA = WSANO_DATA, - - /* no address, look for MX record */ - WSANO_ADDRESS = WSANO_DATA, - NO_ADDRESS = WSANO_ADDRESS -} - -/* - * Windows Sockets errors redefined as regular Berkeley error constants - */ -enum: int -{ - EWOULDBLOCK = WSAEWOULDBLOCK, - EINPROGRESS = WSAEINPROGRESS, - EALREADY = WSAEALREADY, - ENOTSOCK = WSAENOTSOCK, - EDESTADDRREQ = WSAEDESTADDRREQ, - EMSGSIZE = WSAEMSGSIZE, - EPROTOTYPE = WSAEPROTOTYPE, - ENOPROTOOPT = WSAENOPROTOOPT, - EPROTONOSUPPORT = WSAEPROTONOSUPPORT, - ESOCKTNOSUPPORT = WSAESOCKTNOSUPPORT, - EOPNOTSUPP = WSAEOPNOTSUPP, - EPFNOSUPPORT = WSAEPFNOSUPPORT, - EAFNOSUPPORT = WSAEAFNOSUPPORT, - EADDRINUSE = WSAEADDRINUSE, - EADDRNOTAVAIL = WSAEADDRNOTAVAIL, - ENETDOWN = WSAENETDOWN, - ENETUNREACH = WSAENETUNREACH, - ENETRESET = WSAENETRESET, - ECONNABORTED = WSAECONNABORTED, - ECONNRESET = WSAECONNRESET, - ENOBUFS = WSAENOBUFS, - EISCONN = WSAEISCONN, - ENOTCONN = WSAENOTCONN, - ESHUTDOWN = WSAESHUTDOWN, - ETOOMANYREFS = WSAETOOMANYREFS, - ETIMEDOUT = WSAETIMEDOUT, - ECONNREFUSED = WSAECONNREFUSED, - ELOOP = WSAELOOP, - ENAMETOOLONG = WSAENAMETOOLONG, - EHOSTDOWN = WSAEHOSTDOWN, - EHOSTUNREACH = WSAEHOSTUNREACH, - ENOTEMPTY = WSAENOTEMPTY, - EPROCLIM = WSAEPROCLIM, - EUSERS = WSAEUSERS, - EDQUOT = WSAEDQUOT, - ESTALE = WSAESTALE, - EREMOTE = WSAEREMOTE -} - -enum: int -{ - EAI_NONAME = WSAHOST_NOT_FOUND, -} - -int WSAGetLastError() @trusted @nogc; - - -enum: int -{ - AF_UNSPEC = 0, - - AF_UNIX = 1, - AF_INET = 2, - AF_IMPLINK = 3, - AF_PUP = 4, - AF_CHAOS = 5, - AF_NS = 6, - AF_IPX = AF_NS, - AF_ISO = 7, - AF_OSI = AF_ISO, - AF_ECMA = 8, - AF_DATAKIT = 9, - AF_CCITT = 10, - AF_SNA = 11, - AF_DECnet = 12, - AF_DLI = 13, - AF_LAT = 14, - AF_HYLINK = 15, - AF_APPLETALK = 16, - AF_NETBIOS = 17, - AF_VOICEVIEW = 18, - AF_FIREFOX = 19, - AF_UNKNOWN1 = 20, - AF_BAN = 21, - AF_ATM = 22, - AF_INET6 = 23, - AF_CLUSTER = 24, - AF_12844 = 25, - AF_IRDA = 26, - AF_NETDES = 28, - - AF_MAX = 29, - - - PF_UNSPEC = AF_UNSPEC, - - PF_UNIX = AF_UNIX, - PF_INET = AF_INET, - PF_IMPLINK = AF_IMPLINK, - PF_PUP = AF_PUP, - PF_CHAOS = AF_CHAOS, - PF_NS = AF_NS, - PF_IPX = AF_IPX, - PF_ISO = AF_ISO, - PF_OSI = AF_OSI, - PF_ECMA = AF_ECMA, - PF_DATAKIT = AF_DATAKIT, - PF_CCITT = AF_CCITT, - PF_SNA = AF_SNA, - PF_DECnet = AF_DECnet, - PF_DLI = AF_DLI, - PF_LAT = AF_LAT, - PF_HYLINK = AF_HYLINK, - PF_APPLETALK = AF_APPLETALK, - PF_VOICEVIEW = AF_VOICEVIEW, - PF_FIREFOX = AF_FIREFOX, - PF_UNKNOWN1 = AF_UNKNOWN1, - PF_BAN = AF_BAN, - PF_INET6 = AF_INET6, - - PF_MAX = AF_MAX, -} - - -enum: int -{ - SOL_SOCKET = 0xFFFF, -} - - -enum: int -{ - SO_DEBUG = 0x0001, - SO_ACCEPTCONN = 0x0002, - SO_REUSEADDR = 0x0004, - SO_KEEPALIVE = 0x0008, - SO_DONTROUTE = 0x0010, - SO_BROADCAST = 0x0020, - SO_USELOOPBACK = 0x0040, - SO_LINGER = 0x0080, - SO_DONTLINGER = ~SO_LINGER, - SO_OOBINLINE = 0x0100, - SO_SNDBUF = 0x1001, - SO_RCVBUF = 0x1002, - SO_SNDLOWAT = 0x1003, - SO_RCVLOWAT = 0x1004, - SO_SNDTIMEO = 0x1005, - SO_RCVTIMEO = 0x1006, - SO_ERROR = 0x1007, - SO_TYPE = 0x1008, - SO_EXCLUSIVEADDRUSE = ~SO_REUSEADDR, - - TCP_NODELAY = 1, - - IP_MULTICAST_LOOP = 0x4, - IP_ADD_MEMBERSHIP = 0x5, - IP_DROP_MEMBERSHIP = 0x6, - - IPV6_UNICAST_HOPS = 4, - IPV6_MULTICAST_IF = 9, - IPV6_MULTICAST_HOPS = 10, - IPV6_MULTICAST_LOOP = 11, - IPV6_ADD_MEMBERSHIP = 12, - IPV6_DROP_MEMBERSHIP = 13, - IPV6_JOIN_GROUP = IPV6_ADD_MEMBERSHIP, - IPV6_LEAVE_GROUP = IPV6_DROP_MEMBERSHIP, - IPV6_V6ONLY = 27, -} - - -/// Default FD_SETSIZE value. -/// In C/C++, it is redefinable by #define-ing the macro before #include-ing -/// winsock.h. In D, use the $(D FD_CREATE) function to allocate a $(D fd_set) -/// of an arbitrary size. -enum int FD_SETSIZE = 64; - - -struct fd_set_custom(uint SETSIZE) -{ - uint fd_count; - SOCKET[SETSIZE] fd_array; -} - -alias fd_set = fd_set_custom!FD_SETSIZE; - -// Removes. -void FD_CLR(SOCKET fd, fd_set* set) pure @nogc -{ - uint c = set.fd_count; - SOCKET* start = set.fd_array.ptr; - SOCKET* stop = start + c; - - for(; start != stop; start++) - { - if(*start == fd) - goto found; - } - return; //not found - - found: - for(++start; start != stop; start++) - { - *(start - 1) = *start; - } - - set.fd_count = c - 1; -} - - -// Tests. -int FD_ISSET(SOCKET fd, const(fd_set)* set) pure @nogc -{ - const(SOCKET)* start = set.fd_array.ptr; - const(SOCKET)* stop = start + set.fd_count; - - for(; start != stop; start++) - { - if(*start == fd) - return true; - } - return false; -} - - -// Adds. -void FD_SET(SOCKET fd, fd_set* set) pure @nogc -{ - uint c = set.fd_count; - set.fd_array.ptr[c] = fd; - set.fd_count = c + 1; -} - - -// Resets to zero. -void FD_ZERO(fd_set* set) pure @nogc -{ - set.fd_count = 0; -} - - -/// Creates a new $(D fd_set) with the specified capacity. -fd_set* FD_CREATE(uint capacity) pure -{ - // Take into account alignment (SOCKET may be 64-bit and require 64-bit alignment on 64-bit systems) - size_t size = (fd_set_custom!1).sizeof - SOCKET.sizeof + (SOCKET.sizeof * capacity); - auto data = new ubyte[size]; - auto set = cast(fd_set*)data.ptr; - FD_ZERO(set); - return set; -} - -struct linger -{ - ushort l_onoff; - ushort l_linger; -} - - -struct protoent -{ - char* p_name; - char** p_aliases; - short p_proto; -} - - -struct servent -{ - char* s_name; - char** s_aliases; - - version (Win64) - { - char* s_proto; - short s_port; - } - else - { - short s_port; - char* s_proto; - } -} - -/+ -union in6_addr -{ - private union _u_t - { - ubyte[16] Byte; - ushort[8] Word; - } - _u_t u; -} - - -struct in_addr6 -{ - ubyte[16] s6_addr; -} -+/ - -@safe pure @nogc -{ - -version(BigEndian) -{ - ushort htons(ushort x) - { - return x; - } - - - uint htonl(uint x) - { - return x; - } -} -else version(LittleEndian) -{ - private import core.bitop; - - - ushort htons(ushort x) - { - return cast(ushort)((x >> 8) | (x << 8)); - } - - - uint htonl(uint x) - { - return bswap(x); - } -} -else -{ - static assert(0); -} - - -ushort ntohs(ushort x) -{ - return htons(x); -} - - -uint ntohl(uint x) -{ - return htonl(x); -} - -} // @safe pure @nogc - - -enum: int -{ - SOCK_STREAM = 1, - SOCK_DGRAM = 2, - SOCK_RAW = 3, - SOCK_RDM = 4, - SOCK_SEQPACKET = 5, -} - - -enum: int -{ - IPPROTO_IP = 0, - IPPROTO_ICMP = 1, - IPPROTO_IGMP = 2, - IPPROTO_GGP = 3, - IPPROTO_TCP = 6, - IPPROTO_PUP = 12, - IPPROTO_UDP = 17, - IPPROTO_IDP = 22, - IPPROTO_IPV6 = 41, - IPPROTO_ND = 77, - IPPROTO_RAW = 255, - - IPPROTO_MAX = 256, -} - - -enum: int -{ - MSG_OOB = 0x1, - MSG_PEEK = 0x2, - MSG_DONTROUTE = 0x4 -} - - -enum: int -{ - SD_RECEIVE = 0, - SD_SEND = 1, - SD_BOTH = 2, -} - - -enum: uint -{ - INADDR_ANY = 0, - INADDR_LOOPBACK = 0x7F000001, - INADDR_BROADCAST = 0xFFFFFFFF, - INADDR_NONE = 0xFFFFFFFF, - ADDR_ANY = INADDR_ANY, -} - - -enum: int -{ - AI_PASSIVE = 0x1, - AI_CANONNAME = 0x2, - AI_NUMERICHOST = 0x4, - AI_ADDRCONFIG = 0x0400, - AI_NON_AUTHORITATIVE = 0x04000, - AI_SECURE = 0x08000, - AI_RETURN_PREFERRED_NAMES = 0x010000, -} - - -struct timeval -{ - int tv_sec; - int tv_usec; -} - - -union in_addr -{ - private union _S_un_t - { - private struct _S_un_b_t - { - ubyte s_b1, s_b2, s_b3, s_b4; - } - _S_un_b_t S_un_b; - - private struct _S_un_w_t - { - ushort s_w1, s_w2; - } - _S_un_w_t S_un_w; - - uint S_addr; - } - _S_un_t S_un; - - uint s_addr; - - struct - { - ubyte s_net, s_host; - - union - { - ushort s_imp; - - struct - { - ubyte s_lh, s_impno; - } - } - } -} - - -union in6_addr -{ - private union _in6_u_t - { - ubyte[16] u6_addr8; - ushort[8] u6_addr16; - uint[4] u6_addr32; - } - _in6_u_t in6_u; - - ubyte[16] s6_addr8; - ushort[8] s6_addr16; - uint[4] s6_addr32; - - alias s6_addr = s6_addr8; -} - - -const in6_addr IN6ADDR_ANY = { s6_addr8: [0] }; -const in6_addr IN6ADDR_LOOPBACK = { s6_addr8: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] }; -//alias IN6ADDR_ANY_INIT = IN6ADDR_ANY; -//alias IN6ADDR_LOOPBACK_INIT = IN6ADDR_LOOPBACK; - -enum int INET_ADDRSTRLEN = 16; -enum int INET6_ADDRSTRLEN = 46; - - -struct sockaddr -{ - short sa_family; - ubyte[14] sa_data; -} - - -struct sockaddr_in -{ - short sin_family = AF_INET; - ushort sin_port; - in_addr sin_addr; - ubyte[8] sin_zero; -} - - -struct sockaddr_in6 -{ - short sin6_family = AF_INET6; - ushort sin6_port; - uint sin6_flowinfo; - in6_addr sin6_addr; - uint sin6_scope_id; -} - - -struct addrinfo -{ - int ai_flags; - int ai_family; - int ai_socktype; - int ai_protocol; - size_t ai_addrlen; - char* ai_canonname; - sockaddr* ai_addr; - addrinfo* ai_next; -} - - -struct hostent -{ - char* h_name; - char** h_aliases; - short h_addrtype; - short h_length; - char** h_addr_list; - - - char* h_addr() @safe pure nothrow @nogc - { - return h_addr_list[0]; - } -} - -// Note: These are Winsock2!! -struct WSAOVERLAPPED; -alias LPWSAOVERLAPPED = WSAOVERLAPPED*; -alias LPWSAOVERLAPPED_COMPLETION_ROUTINE = void function(uint, uint, LPWSAOVERLAPPED, uint); -int WSAIoctl(SOCKET s, uint dwIoControlCode, - void* lpvInBuffer, uint cbInBuffer, - void* lpvOutBuffer, uint cbOutBuffer, - uint* lpcbBytesReturned, - LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); - - -enum IOC_VENDOR = 0x18000000; -enum SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4; - -/* Argument structure for SIO_KEEPALIVE_VALS */ -struct tcp_keepalive -{ - uint onoff; - uint keepalivetime; - uint keepaliveinterval; -} +version (Windows): +public import core.sys.windows.winsock2; diff --git a/std/compiler.d b/std/compiler.d index b469bffb541..25936c94aec 100644 --- a/std/compiler.d +++ b/std/compiler.d @@ -7,7 +7,7 @@ * WIKI = Phobos/StdCompiler * * Copyright: Copyright Digital Mars 2000 - 2011. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright), Alex Rønne Petersen * Source: $(PHOBOSSRC std/_compiler.d) */ diff --git a/std/complex.d b/std/complex.d index 352a000ce73..10e2caadf3f 100644 --- a/std/complex.d +++ b/std/complex.d @@ -14,9 +14,7 @@ */ module std.complex; - -import std.format, std.math, std.numeric, std.traits; - +import std.traits; /** Helper function that returns a _complex number with the specified real and imaginary parts. @@ -24,23 +22,6 @@ import std.format, std.math, std.numeric, std.traits; If neither $(D re) nor $(D im) are floating-point numbers, this function returns a $(D Complex!double). Otherwise, the return type is deduced using $(D std.traits.CommonType!(R, I)). - - Examples: - --- - auto c = complex(2.0); - static assert (is(typeof(c) == Complex!double)); - assert (c.re == 2.0); - assert (c.im == 0.0); - - auto w = complex(2); - static assert (is(typeof(w) == Complex!double)); - assert (w == c); - - auto z = complex(1, 3.14L); - static assert (is(typeof(z) == Complex!real)); - assert (z.re == 1.0L); - assert (z.im == 3.14L); - --- */ auto complex(T)(T re) @safe pure nothrow @nogc if (is(T : double)) { @@ -60,6 +41,7 @@ auto complex(R, I)(R re, I im) @safe pure nothrow @nogc return Complex!double(re, im); } +/// unittest { auto a = complex(1.0); @@ -104,6 +86,8 @@ unittest */ struct Complex(T) if (isFloatingPoint!T) { + import std.format : FormatSpec; + /** The real part of the number. */ T re; @@ -141,7 +125,7 @@ struct Complex(T) if (isFloatingPoint!T) // Formatting with std.string.format specs: the precision and width // specifiers apply to both the real and imaginary parts of the // complex number. - import std.string : format; + import std.format : format; assert(format("%.2f", c) == "1.20+3.40i"); assert(format("%4.1f", c) == " 1.2+ 3.4i"); } @@ -150,34 +134,14 @@ struct Complex(T) if (isFloatingPoint!T) void toString(Char)(scope void delegate(const(Char)[]) sink, FormatSpec!Char formatSpec) const { + import std.math : signbit; + import std.format : formatValue; formatValue(sink, re, formatSpec); if (signbit(im) == 0) sink("+"); formatValue(sink, im, formatSpec); sink("i"); } - /* - * Explicitly undocumented. It will be removed in October 2014. - * Please use $(XREF string,format) instead. - */ - deprecated("Please use std.string.format instead.") - string toString(scope void delegate(const(char)[]) sink, - string formatSpec = "%s") - const - { - if (sink == null) - { - import std.exception : assumeUnique; - char[] buf; - buf.reserve(100); - formattedWrite((const(char)[] s) { buf ~= s; }, formatSpec, this); - return assumeUnique(buf); - } - - formattedWrite(sink, formatSpec, this); - return null; - } - @safe pure nothrow @nogc: this(R : T)(Complex!R z) @@ -283,21 +247,20 @@ struct Complex(T) if (isFloatingPoint!T) Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const if (op == "/" && isNumeric!R) { - typeof(return) w; - alias Tmp = FPTemporary!(typeof(w.re)); - + import std.math : fabs; + typeof(return) w = void; if (fabs(re) < fabs(im)) { - Tmp ratio = re/im; - Tmp rdivd = r/(re*ratio + im); + immutable ratio = re/im; + immutable rdivd = r/(re*ratio + im); w.re = rdivd*ratio; w.im = -rdivd; } else { - Tmp ratio = im/re; - Tmp rdivd = r/(re + im*ratio); + immutable ratio = im/re; + immutable rdivd = r/(re + im*ratio); w.re = rdivd; w.im = -rdivd*ratio; @@ -310,7 +273,8 @@ struct Complex(T) if (isFloatingPoint!T) Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R lhs) const if (op == "^^" && isNumeric!R) { - FPTemporary!(CommonType!(T, R)) ab = void, ar = void; + import std.math : log, exp, PI; + Unqual!(CommonType!(T, R)) ab = void, ar = void; if (lhs >= 0) { @@ -355,21 +319,22 @@ struct Complex(T) if (isFloatingPoint!T) ref Complex opOpAssign(string op, C)(C z) if (op == "/" && is(C R == Complex!R)) { + import std.math : fabs; if (fabs(z.re) < fabs(z.im)) { - FPTemporary!T ratio = z.re/z.im; - FPTemporary!T denom = z.re*ratio + z.im; + immutable ratio = z.re/z.im; + immutable denom = z.re*ratio + z.im; - auto temp = (re*ratio + im)/denom; + immutable temp = (re*ratio + im)/denom; im = (im*ratio - re)/denom; re = temp; } else { - FPTemporary!T ratio = z.im/z.re; - FPTemporary!T denom = z.re + z.im*ratio; + immutable ratio = z.im/z.re; + immutable denom = z.re + z.im*ratio; - auto temp = (re + im*ratio)/denom; + immutable temp = (re + im*ratio)/denom; im = (im - re*ratio)/denom; re = temp; } @@ -380,10 +345,11 @@ struct Complex(T) if (isFloatingPoint!T) ref Complex opOpAssign(string op, C)(C z) if (op == "^^" && is(C R == Complex!R)) { - FPTemporary!T r = abs(this); - FPTemporary!T t = arg(this); - FPTemporary!T ab = r^^z.re * exp(-t*z.im); - FPTemporary!T ar = t*z.re + log(r)*z.im; + import std.math : exp, log; + immutable r = abs(this); + immutable t = arg(this); + immutable ab = r^^z.re * exp(-t*z.im); + immutable ar = t*z.re + log(r)*z.im; re = ab*std.math.cos(ar); im = ab*std.math.sin(ar); @@ -411,8 +377,8 @@ struct Complex(T) if (isFloatingPoint!T) ref Complex opOpAssign(string op, R)(R r) if (op == "^^" && isFloatingPoint!R) { - FPTemporary!T ab = abs(this)^^r; - FPTemporary!T ar = arg(this)*r; + immutable ab = abs(this)^^r; + immutable ar = arg(this)*r; re = ab*std.math.cos(ar); im = ab*std.math.sin(ar); return this; @@ -448,6 +414,9 @@ struct Complex(T) if (isFloatingPoint!T) unittest { + import std.math; + import std.complex; + enum EPS = double.epsilon; auto c1 = complex(1.0, 1.0); @@ -633,25 +602,6 @@ unittest assert (z.re == 2.0 && z.im == 2.0); } -deprecated unittest -{ - // Convert to string. - - // Using default format specifier - auto z1 = Complex!real(0.123456789, 0.123456789); - char[] s1; - z1.toString((const(char)[] c) { s1 ~= c; }); - assert (s1 == "0.123457+0.123457i"); - assert (s1 == z1.toString()); - - // Using custom format specifier - auto z2 = conj(z1); - char[] s2; - z2.toString((const(char)[] c) { s2 ~= c; }, "%.8e"); - assert (s2 == "1.23456789e-01-1.23456789e-01i"); - assert (s2 == z2.toString(null, "%.8e")); -} - /* Makes Complex!(Complex!T) fold to Complex!T. @@ -692,6 +642,7 @@ unittest /** Calculates the absolute value (or modulus) of a complex number. */ T abs(T)(Complex!T z) @safe pure nothrow @nogc { + import std.math : hypot; return hypot(z.re, z.im); } @@ -714,6 +665,7 @@ T sqAbs(T)(Complex!T z) @safe pure nothrow @nogc unittest { + import std.math; assert (sqAbs(complex(0.0)) == 0.0); assert (sqAbs(complex(1.0)) == 1.0); assert (sqAbs(complex(0.0, 1.0)) == 1.0); @@ -731,6 +683,7 @@ T sqAbs(T)(T x) @safe pure nothrow @nogc unittest { + import std.math; assert (sqAbs(0.0) == 0.0); assert (sqAbs(-1.0) == 1.0); assert (approxEqual(sqAbs(-3.0L), 9.0L)); @@ -741,11 +694,13 @@ unittest /** Calculates the argument (or phase) of a complex number. */ T arg(T)(Complex!T z) @safe pure nothrow @nogc { + import std.math : atan2; return atan2(z.im, z.re); } unittest { + import std.math; assert (arg(complex(1.0)) == 0.0); assert (arg(complex(0.0L, 1.0L)) == PI_2); assert (arg(complex(1.0L, 1.0L)) == PI_4); @@ -775,6 +730,7 @@ Complex!(CommonType!(T, U)) fromPolar(T, U)(T modulus, U argument) unittest { + import std.math; auto z = fromPolar(std.math.sqrt(2.0), PI_4); assert (approxEqual(z.re, 1.0L, real.epsilon)); assert (approxEqual(z.im, 1.0L, real.epsilon)); @@ -784,6 +740,7 @@ unittest /** Trigonometric functions. */ Complex!T sin(T)(Complex!T z) @safe pure nothrow @nogc { + import std.math : expi, coshisinh; auto cs = expi(z.re); auto csh = coshisinh(z.im); return typeof(return)(cs.im * csh.re, cs.re * csh.im); @@ -799,12 +756,15 @@ unittest /// ditto Complex!T cos(T)(Complex!T z) @safe pure nothrow @nogc { + import std.math : expi, coshisinh; auto cs = expi(z.re); auto csh = coshisinh(z.im); return typeof(return)(cs.re * csh.re, - cs.im * csh.im); } unittest{ + import std.math; + import std.complex; assert(cos(complex(0.0)) == 1.0); assert(cos(complex(1.3L)) == std.math.cos(1.3L)); assert(cos(complex(0, 5.2L)) == cosh(5.2L)); @@ -821,7 +781,8 @@ unittest{ */ Complex!real expi(real y) @trusted pure nothrow @nogc { - return Complex!real(std.math.cos(y), std.math.sin(y)); + import std.math : cos, sin; + return Complex!real(cos(y), sin(y)); } unittest @@ -837,6 +798,7 @@ unittest /** Square root. */ Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc { + import std.math : fabs; typeof(return) c; real x,y,w,r; @@ -888,7 +850,7 @@ unittest // Issue 10881: support %f formatting of complex numbers unittest { - import std.string : format; + import std.format : format; auto x = complex(1.2, 3.4); assert(format("%.2f", x) == "1.20+3.40i"); @@ -900,6 +862,7 @@ unittest unittest { // Test wide string formatting + import std.format; wstring wformat(T)(string format, Complex!T c) { import std.array : appender; diff --git a/std/concurrency.d b/std/concurrency.d index 817440246d0..22b051ac116 100644 --- a/std/concurrency.d +++ b/std/concurrency.d @@ -1,23 +1,30 @@ /** * This is a low-level messaging API upon which more structured or restrictive * APIs may be built. The general idea is that every messageable entity is - * represented by a common handle type (called a Cid in this implementation), - * which allows messages to be sent to in-process threads, on-host processes, - * and foreign-host processes using the same interface. This is an important + * represented by a common handle type called a Tid, which allows messages to + * be sent to logical threads that are executing in both the current process + * and in external processes using the same interface. This is an important * aspect of scalability because it allows the components of a program to be * spread across available resources with few to no changes to the actual * implementation. * - * Right now, only in-process threads are supported and referenced by a more - * specialized handle called a Tid. It is effectively a subclass of Cid, with - * additional features specific to in-process messaging. + * A logical thread is an execution context that has its own stack and which + * runs asynchronously to other logical threads. These may be preemptively + * scheduled kernel threads, fibers (cooperative user-space threads), or some + * other concept with similar behavior. + * + * The type of concurrency used when logical threads are created is determined + * by the Scheduler selected at initialization time. The default behavior is + * currently to create a new kernel thread per call to spawn, but other + * schedulers are available that multiplex fibers across the main thread or + * use some combination of the two approaches. * * Synposis: * --- * import std.stdio; * import std.concurrency; * - * void spawnedFunc(Tid tid) + * void spawnedFunc(Tid ownerTid) * { * // Receive a message from the owner thread. * receive( @@ -26,16 +33,16 @@ * * // Send a message back to the owner thread * // indicating success. - * send(tid, true); + * send(ownerTid, true); * } * * void main() * { * // Start spawnedFunc in a new thread. - * auto tid = spawn(&spawnedFunc, thisTid); + * auto childTid = spawn(&spawnedFunc, thisTid); * * // Send the number 42 to this new thread. - * send(tid, 42); + * send(childTid, 42); * * // Receive the result code. * auto wasSuccessful = receiveOnly!(bool); @@ -44,12 +51,12 @@ * } * --- * - * Copyright: Copyright Sean Kelly 2009 - 2010. + * Copyright: Copyright Sean Kelly 2009 - 2014. * License: Boost License 1.0. - * Authors: Sean Kelly, Alex Rønne Petersen + * Authors: Sean Kelly, Alex Rønne Petersen, Martin Nowak * Source: $(PHOBOSSRC std/_concurrency.d) */ -/* Copyright Sean Kelly 2009 - 2010. +/* Copyright Sean Kelly 2009 - 2014. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) @@ -63,6 +70,7 @@ public } private { + import core.atomic; import core.thread; import core.sync.mutex; import core.sync.condition; @@ -80,8 +88,8 @@ private static if( !T.length ) enum hasLocalAliasing = false; else - enum hasLocalAliasing = (std.traits.hasLocalAliasing!(T[0]) && !is(T[0] == Tid)) || - std.concurrency.hasLocalAliasing!(T[1 .. $]); + enum hasLocalAliasing = (std.traits.hasUnsharedAliasing!(T[0]) && !is(T[0] == Tid)) || + std.concurrency.hasLocalAliasing!(T[1 .. $]); } enum MsgType @@ -185,23 +193,18 @@ private } } - MessageBox mbox; - bool[Tid] links; - Tid owner; + @property ref ThreadInfo thisInfo() + { + if( scheduler is null ) + return ThreadInfo.thisInfo; + return scheduler.thisInfo; + } } static ~this() { - if( mbox !is null ) - { - mbox.close(); - auto me = thisTid; - foreach( tid; links.keys ) - _send( MsgType.linkDead, tid, me ); - if( owner != Tid.init ) - _send( MsgType.linkDead, owner, me ); - } + thisInfo.cleanup(); } @@ -309,43 +312,65 @@ class TidMissingException : Exception /** - * An opaque type used to represent a logical local process. + * An opaque type used to represent a logical thread. */ struct Tid { private: - this( MessageBox m ) + this( MessageBox m ) @safe { mbox = m; } MessageBox mbox; + +public: + + /** + * Generate a convenient string for identifying this Tid. This is only + * useful to see if Tid's that are currently executing are the same or + * different, e.g. for logging and debugging. It is potentially possible + * that a Tid executed in the future will have the same toString() output + * as another Tid that has already terminated. + */ + void toString(scope void delegate(const(char)[]) sink) + { + import std.format; + formattedWrite(sink, "Tid(%x)", &mbox); + } + } /** * Returns the caller's Tid. */ -@property Tid thisTid() +@property Tid thisTid() @safe { - if( mbox ) - return Tid( mbox ); - mbox = new MessageBox; - return Tid( mbox ); + // TODO: remove when concurrency is safe + auto trus = delegate() @trusted + { + if( thisInfo.ident != Tid.init ) + return thisInfo.ident; + thisInfo.ident = Tid( new MessageBox ); + return thisInfo.ident; + }; + + return trus(); } /** - * Return the Tid of the thread which - * spawned the caller's thread. + * Return the Tid of the thread which spawned the caller's thread. * * Throws: A $(D TidMissingException) exception if * there is no owner thread. */ @property Tid ownerTid() { - enforce!TidMissingException(owner.mbox !is null, "Error: Thread has no owner thread."); - return owner; + enforce!TidMissingException(thisInfo.owner.mbox !is null, + "Error: Thread has no owner thread."); + return thisInfo.owner; } unittest @@ -391,18 +416,20 @@ private template isSpawnable(F, T...) } /** - * Executes the supplied function in a new context represented by $(D Tid). The - * calling context is designated as the owner of the new context. When the - * owner context terminated an $(D OwnerTerminated) message will be sent to the - * new context, causing an $(D OwnerTerminated) exception to be thrown on - * $(D receive()). + * Starts fn(args) in a new logical thread. + * + * Executes the supplied function in a new logical thread represented by + * $(D Tid). The calling thread is designated as the owner of the new thread. + * When the owner thread terminates an $(D OwnerTerminated) message will be + * sent to the new thread, causing an $(D OwnerTerminated) exception to be + * thrown on $(D receive()). * * Params: * fn = The function to execute. * args = Arguments to the function. * * Returns: - * A Tid representing the new context. + * A Tid representing the new logical thread. * * Notes: * $(D args) must not have unshared aliasing. In other words, all arguments @@ -446,11 +473,14 @@ Tid spawn(F, T...)( F fn, T args ) /** - * Executes the supplied function in a new context represented by Tid. This - * new context is linked to the calling context so that if either it or the - * calling context terminates a LinkTerminated message will be sent to the - * other, causing a LinkTerminated exception to be thrown on receive(). The - * owner relationship from spawn() is preserved as well, so if the link + * Starts fn(args) in a logical thread and will receive a LinkTerminated + * message when the operation terminates. + * + * Executes the supplied function in a new logical thread represented by + * Tid. This new thread is linked to the calling thread so that if either + * it or the calling thread terminates a LinkTerminated message will be sent + * to the other, causing a LinkTerminated exception to be thrown on receive(). + * The owner relationship from spawn() is preserved as well, so if the link * between threads is broken, owner termination will still result in an * OwnerTerminated exception to be thrown on receive(). * @@ -459,7 +489,7 @@ Tid spawn(F, T...)( F fn, T args ) * args = Arguments to the function. * * Returns: - * A Tid representing the new context. + * A Tid representing the new thread. */ Tid spawnLinked(F, T...)( F fn, T args ) if ( isSpawnable!(F, T) ) @@ -482,14 +512,20 @@ private Tid _spawn(F, T...)( bool linked, F fn, T args ) void exec() { - mbox = spawnTid.mbox; - owner = ownerTid; + thisInfo.ident = spawnTid; + thisInfo.owner = ownerTid; fn( args ); } // TODO: MessageList and &exec should be shared. - auto t = new Thread( &exec ); t.start(); - links[spawnTid] = linked; + if( scheduler !is null ) + scheduler.spawn( &exec ); + else + { + auto t = new Thread( &exec ); + t.start(); + } + thisInfo.links[spawnTid] = linked; return spawnTid; } @@ -546,7 +582,9 @@ unittest /** - * Sends the supplied value to the context represented by tid. As with + * Places the values as a message at the back of tid's message queue. + * + * Sends the supplied value to the thread represented by tid. As with * $(XREF concurrency, spawn), $(D T) must not have unshared aliasing. */ void send(T...)( Tid tid, T vals ) @@ -558,6 +596,8 @@ void send(T...)( Tid tid, T vals ) /** + * Places the values as a message on the front of tid's message queue. + * * Send a message to $(D tid) but place it at the front of $(D tid)'s message * queue instead of at the back. This function is typically used for * out-of-band communication, to signal exceptional conditions, etc. @@ -591,6 +631,8 @@ private void _send(T...)( MsgType type, Tid tid, T vals ) /** + * Receives a message from another thread. + * * Receive a message from another thread, or block if no messages of the * specified types are available. This function works by pattern matching * a message against a set of delegates and executing the first match found. @@ -626,13 +668,15 @@ private void _send(T...)( MsgType type, Tid tid, T vals ) void receive(T...)( T ops ) in { - assert(mbox !is null, "Cannot receive a message until a thread was spawned "~ + assert(thisInfo.ident.mbox !is null, + "Cannot receive a message until a thread was spawned " "or thisTid was passed to a running thread."); } body { checkops( ops ); - mbox.get( ops ); + + thisInfo.ident.mbox.get( ops ); } @@ -708,37 +752,39 @@ private template receiveOnlyRet(T...) receiveOnlyRet!(T) receiveOnly(T...)() in { - assert(mbox !is null, "Cannot receive a message until a thread was spawned "~ + assert(thisInfo.ident.mbox !is null, + "Cannot receive a message until a thread was spawned " "or thisTid was passed to a running thread."); } body { Tuple!(T) ret; - mbox.get( ( T val ) - { - static if( T.length ) - ret.field = val; - }, - ( LinkTerminated e ) - { - throw e; - }, - ( OwnerTerminated e ) - { - throw e; - }, - ( Variant val ) - { - static if (T.length > 1) - string exp = T.stringof; - else - string exp = T[0].stringof; - - throw new MessageMismatch( - format("Unexpected message type: expected '%s', got '%s'", - exp, val.type.toString())); - } ); + thisInfo.ident.mbox.get( + ( T val ) + { + static if( T.length ) + ret.field = val; + }, + ( LinkTerminated e ) + { + throw e; + }, + ( OwnerTerminated e ) + { + throw e; + }, + ( Variant val ) + { + static if (T.length > 1) + string exp = T.stringof; + else + string exp = T[0].stringof; + + throw new MessageMismatch( + format("Unexpected message type: expected '%s', got '%s'", + exp, val.type.toString())); + } ); static if( T.length == 1 ) return ret[0]; else @@ -766,45 +812,49 @@ unittest assert(result == "Unexpected message type: expected 'string', got 'int'"); } -/++ - Same as $(D receive) except that rather than wait forever for a message, - it waits until either it receives a message or the given - $(CXREF time, Duration) has passed. It returns $(D true) if it received a - message and $(D false) if it timed out waiting for one. - +/ +/** + * Tries to receive but will give up if no matches arrive within duration. + * + * Same as $(D receive) except that rather than wait forever for a message, + * it waits until either it receives a message or the given + * $(CXREF time, Duration) has passed. It returns $(D true) if it received a + * message and $(D false) if it timed out waiting for one. + */ bool receiveTimeout(T...)( Duration duration, T ops ) in { - assert(mbox !is null, "Cannot receive a message until a thread was spawned "~ + assert(thisInfo.ident.mbox !is null, + "Cannot receive a message until a thread was spawned " "or thisTid was passed to a running thread."); } body { checkops( ops ); - return mbox.get( duration, ops ); + + return thisInfo.ident.mbox.get( duration, ops ); } unittest { assert( __traits( compiles, { - receiveTimeout( dur!"msecs"(0), (Variant x) {} ); - receiveTimeout( dur!"msecs"(0), (int x) {}, (Variant x) {} ); + receiveTimeout( msecs(0), (Variant x) {} ); + receiveTimeout( msecs(0), (int x) {}, (Variant x) {} ); } ) ); assert( !__traits( compiles, { - receiveTimeout( dur!"msecs"(0), (Variant x) {}, (int x) {} ); + receiveTimeout( msecs(0), (Variant x) {}, (int x) {} ); } ) ); assert( !__traits( compiles, { - receiveTimeout( dur!"msecs"(0), (int x) {}, (int x) {} ); + receiveTimeout( msecs(0), (int x) {}, (int x) {} ); } ) ); assert( __traits( compiles, { - receiveTimeout( dur!"msecs"(10), (int x) {}, (Variant x) {} ); + receiveTimeout( msecs(10), (int x) {}, (Variant x) {} ); } ) ); } @@ -847,6 +897,8 @@ private /** + * Sets a maximum mailbox size. + * * Sets a limit on the maximum number of user messages allowed in the mailbox. * If this limit is reached, the caller attempting to add a new message will * execute the behavior specified by doThis. If messages is zero, the mailbox @@ -873,6 +925,8 @@ void setMaxMailboxSize( Tid tid, size_t messages, OnCrowding doThis ) /** + * Sets a maximum mailbox size. + * * Sets a limit on the maximum number of user messages allowed in the mailbox. * If this limit is reached, the caller attempting to add a new message will * execute onCrowdingDoThis. If messages is zero, the mailbox is unbounded. @@ -908,7 +962,7 @@ shared static this() } -static ~this() +private void unregisterMe() { auto me = thisTid; @@ -925,6 +979,8 @@ static ~this() /** + * Associates name with tid. + * * Associates name with tid in a process-local map. When the thread * represented by tid terminates, any names associated with it will be * automatically unregistered. @@ -998,6 +1054,711 @@ Tid locate( string name ) } +////////////////////////////////////////////////////////////////////////////// +// Scheduler +////////////////////////////////////////////////////////////////////////////// + + +/** + * Encapsulates all implementation-level data needed for scheduling. + * + * When definining a Scheduler, an instance of this struct must be associated + * with each logical thread. It contains all implementation-level information + * needed by the internal API. + */ +struct ThreadInfo +{ + Tid ident; + bool[Tid] links; + Tid owner; + + /** + * Gets a thread-local instance of ThreadInfo. + * + * Gets a thread-local instance of ThreadInfo, which should be used as the + * default instance when info is requested for a thread not created by the + * Scheduler. + */ + static @property ref thisInfo() nothrow + { + static ThreadInfo val; + return val; + } + + + /** + * Cleans up this ThreadInfo. + * + * This must be called when a scheduled thread terminates. It tears down + * the messaging system for the thread and notifies interested parties of + * the thread's termination. + */ + void cleanup() + { + if( ident.mbox !is null ) + ident.mbox.close(); + foreach( tid; links.keys ) + _send( MsgType.linkDead, tid, ident ); + if( owner != Tid.init ) + _send( MsgType.linkDead, owner, ident ); + unregisterMe(); // clean up registry entries + } +} + + +/** + * A Scheduler controls how threading is performed by spawn. + * + * Implementing a Scheduler allows the concurrency mechanism used by this + * module to be customized according to different needs. By default, a call + * to spawn will create a new kernel thread that executes the supplied routine + * and terminates when finished. But it is possible to create Schedulers that + * reuse threads, that multiplex Fibers (coroutines) across a single thread, + * or any number of other approaches. By making the choice of Scheduler a + * user-level option, std.concurrency may be used for far more types of + * application than if this behavior were predefined. + * + * Example: + * --- + * import std.concurrency; + * import std.stdio; + * + * void main() + * { + * scheduler = new FiberScheduler; + * scheduler.start( + * { + * writeln("the rest of main goes here"); + * }); + * } + * --- + * + * Some schedulers have a dispatching loop that must run if they are to work + * properly, so for the sake of consistency, when using a scheduler, start() + * must be called within main(). This yields control to the scheduler and + * will ensure that any spawned threads are executed in an expected manner. + */ +interface Scheduler +{ + /** + * Spawns the supplied op and starts the Scheduler. + * + * This is intended to be called at the start of the program to yield all + * scheduling to the active Scheduler instance. This is necessary for + * schedulers that explicitly dispatch threads rather than simply relying + * on the operating system to do so, and so start should always be called + * within main() to begin normal program execution. + * + * Params: + * op = A wrapper for whatever the main thread would have done in the + * absence of a custom scheduler. It will be automatically executed + * via a call to spawn by the Scheduler. + */ + void start( void delegate() op ); + + /** + * Assigns a logical thread to execute the supplied op. + * + * This routine is called by spawn. It is expected to instantiate a new + * logical thread and run the supplied operation. This thread must call + * thisInfo.cleanup() when the thread terminates if the scheduled thread + * is not a kernel thread--all kernel threads will have their ThreadInfo + * cleaned up automatically by a thread-local destructor. + * + * Params: + * op = The function to execute. This may be the actual function passed + * by the user to spawn itself, or may be a wrapper function. + */ + void spawn( void delegate() op ); + + /** + * Yields execution to another logical thread. + * + * This routine is called at various points within concurrency-aware APIs + * to provide a scheduler a chance to yield execution when using some sort + * of cooperative multithreading model. If this is not appropriate, such + * as when each logical thread is backed by a dedicated kernel thread, + * this routine may be a no-op. + */ + void yield() nothrow; + + /** + * Returns an appropriate ThreadInfo instance. + * + * Returns an instance of ThreadInfo specific to the logical thread that + * is calling this routine or, if the calling thread was not create by + * this scheduler, returns ThreadInfo.thisInfo instead. + */ + @property ref ThreadInfo thisInfo() nothrow; + + /** + * Creates a Condition varialbe analog for signaling. + * + * Creates a new Condition variable analog which is used to check for and + * to signal the addition of messages to a thread's message queue. Like + * yield, some schedulers may need to define custom behavior so that calls + * to Condition.wait() yield to another thread when no new messages are + * available instead of blocking. + * + * Params: + * m = The Mutex that will be associated with this condition. It will be + * locked prior to any operation on the condition, and so in some + * cases a Scheduler may need to hold this reference and unlock the + * mutex before yielding execution to another logical thread. + */ + Condition newCondition( Mutex m ) nothrow; +} + + +/** + * An example Scheduler using kernel threads. + * + * This is an example Scheduler that mirrors the default scheduling behavior + * of creating one kernel thread per call to spawn. It is fully functional + * and may be instantiated and used, but is not a necessary part of the + * default functioning of this module. + */ +class ThreadScheduler : + Scheduler +{ + /** + * This simply runs op directly, since no real scheduling is needed by + * this approach. + */ + void start( void delegate() op ) + { + op(); + } + + + /** + * Creates a new kernel thread and assigns it to run the supplied op. + */ + void spawn( void delegate() op ) + { + auto t = new Thread( op ); + t.start(); + } + + + /** + * This scheduler does no explicit multiplexing, so this is a no-op. + */ + void yield() nothrow + { + // no explicit yield needed + } + + + /** + * Returns ThreadInfo.thisInfo, since it is a thread-local instance of + * ThreadInfo, which is the correct behavior for this scheduler. + */ + @property ref ThreadInfo thisInfo() nothrow + { + return ThreadInfo.thisInfo; + } + + + /** + * Creates a new Condition variable. No custom behavior is needed here. + */ + Condition newCondition( Mutex m ) nothrow + { + return new Condition( m ); + } +} + + +/** + * An example Scheduler using Fibers. + * + * This is an example scheduler that creates a new Fiber per call to spawn + * and multiplexes the execution of all fibers within the main thread. + */ +class FiberScheduler : + Scheduler +{ + /** + * This creates a new Fiber for the supplied op and then starts the + * dispatcher. + */ + void start( void delegate() op ) + { + create( op ); + dispatch(); + } + + + /** + * This created a new Fiber for the supplied op and adds it to the + * dispatch list. + */ + void spawn( void delegate() op ) nothrow + { + create( op ); + yield(); + } + + + /** + * If the caller is a scheduled Fiber, this yields execution to another + * scheduled Fiber. + */ + void yield() nothrow + { + // NOTE: It's possible that we should test whether the calling Fiber + // is an InfoFiber before yielding, but I think it's reasonable + // that any (non-Generator) fiber should yield here. + if(Fiber.getThis()) + Fiber.yield(); + } + + + /** + * Returns an appropriate ThreadInfo instance. + * + * Returns a ThreadInfo instance specific to the calling Fiber if the + * Fiber was created by this dispatcher, otherwise it returns + * ThreadInfo.thisInfo. + */ + @property ref ThreadInfo thisInfo() nothrow + { + auto f = cast(InfoFiber) Fiber.getThis(); + + if( f !is null ) + return f.info; + return ThreadInfo.thisInfo; + } + + + /** + * Returns a Condition analog that yields when wait or notify is called. + */ + Condition newCondition( Mutex m ) nothrow + { + return new FiberCondition( m ); + } + + +private: + static class InfoFiber : + Fiber + { + ThreadInfo info; + + this( void delegate() op ) nothrow + { + super( op ); + } + } + + + class FiberCondition : + Condition + { + this( Mutex m ) nothrow + { + super(m); + notified = false; + } + + override void wait() nothrow + { + scope(exit) notified = false; + + while( !notified ) + switchContext(); + } + + override bool wait( Duration period ) nothrow + { + scope(exit) notified = false; + + for( auto limit = Clock.currSystemTick + period; + !notified && !period.isNegative; + period = limit - Clock.currSystemTick ) + { + yield(); + } + return notified; + } + + override void notify() nothrow + { + notified = true; + switchContext(); + } + + override void notifyAll() nothrow + { + notified = true; + switchContext(); + } + + private: + final void switchContext() nothrow + { + mutex.unlock(); + scope(exit) mutex.lock(); + yield(); + } + + private bool notified; + } + + +private: + final void dispatch() + { + import std.algorithm : remove; + + while( m_fibers.length > 0 ) + { + auto t = m_fibers[m_pos].call( Fiber.Rethrow.no ); + if (t !is null && !(cast(OwnerTerminated) t)) + throw t; + if( m_fibers[m_pos].state == Fiber.State.TERM ) + { + if( m_pos >= (m_fibers = remove( m_fibers, m_pos )).length ) + m_pos = 0; + } + else if( m_pos++ >= m_fibers.length - 1 ) + { + m_pos = 0; + } + } + } + + + final void create( void delegate() op ) nothrow + { + void wrap() + { + scope(exit) + { + thisInfo.cleanup(); + } + op(); + } + m_fibers ~= new InfoFiber( &wrap ); + } + + +private: + Fiber[] m_fibers; + size_t m_pos; +} + + +unittest +{ + static void receive(Condition cond, ref size_t received) + { + while (true) + { + synchronized (cond.mutex) + { + cond.wait(); + ++received; + } + } + } + + static void send(Condition cond, ref size_t sent) + { + while (true) + { + synchronized (cond.mutex) + { + ++sent; + cond.notify(); + } + } + } + + auto fs = new FiberScheduler; + auto mtx = new Mutex; + auto cond = fs.newCondition(mtx); + + size_t received, sent; + auto waiter = new Fiber({receive(cond, received);}), notifier = new Fiber({send(cond, sent);}); + waiter.call(); + assert(received == 0); + notifier.call(); + assert(sent == 1); + assert(received == 0); + waiter.call(); + assert(received == 1); + waiter.call(); + assert(received == 1); +} + + +/** + * Sets the Scheduler behavior within the program. + * + * This variable sets the Scheduler behavior within this program. Typically, + * when setting a Scheduler, scheduler.start() should be called in main. This + * routine will not return until program execution is complete. + */ +__gshared Scheduler scheduler; + + +////////////////////////////////////////////////////////////////////////////// +// Generator +////////////////////////////////////////////////////////////////////////////// + + +/** + * If the caller is a Fiber and is not a Generator, this function will call + * scheduler.yield() or Fiber.yield(), as appropriate. + */ +void yield() nothrow +{ + auto fiber = Fiber.getThis(); + if (!(cast(IsGenerator) fiber)) + { + if (scheduler is null) + { + if (fiber) + return Fiber.yield(); + } + else scheduler.yield(); + } +} + + +/// Used to determine whether a Generator is running. +private interface IsGenerator {} + + +/** + * A Generator is a Fiber that periodically returns values of type T to the + * caller via yield. This is represented as an InputRange. + * + * Example: + * --- + * import std.concurrency; + * import std.stdio; + * + * + * void main() + * { + * auto tid = spawn( + * { + * while (true) + * { + * writeln(receiveOnly!int()); + * } + * }); + * + * auto r = new Generator!int( + * { + * foreach (i; 1 .. 10) + * yield(i); + * }); + * + * foreach (e; r) + * { + * tid.send(e); + * } + * } + * --- + */ +class Generator(T) : + Fiber, IsGenerator +{ + /** + * Initializes a generator object which is associated with a static + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * fn = The fiber function. + * + * In: + * fn must not be null. + */ + this(void function() fn) + { + super(fn); + call(); + } + + + /** + * Initializes a generator object which is associated with a static + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * fn = The fiber function. + * sz = The stack size for this fiber. + * + * In: + * fn must not be null. + */ + this(void function() fn, size_t sz) + { + super(fn, sz); + call(); + } + + + /** + * Initializes a generator object which is associated with a dynamic + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * dg = The fiber function. + * + * In: + * dg must not be null. + */ + this(void delegate() dg) + { + super(dg); + call(); + } + + + /** + * Initializes a generator object which is associated with a dynamic + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * dg = The fiber function. + * sz = The stack size for this fiber. + * + * In: + * dg must not be null. + */ + this(void delegate() dg, size_t sz) + { + super(dg, sz); + call(); + } + + + /** + * Returns true if the generator is empty. + */ + final bool empty() @property + { + return m_value is null || state == State.TERM; + } + + + /** + * Obtains the next value from the underlying function. + */ + final void popFront() + { + call(); + } + + + /** + * Returns the most recently generated value. + */ + final T front() @property + { + return *m_value; + } + + +private: + T* m_value; +} + + +/** + * Yields a value of type T to the caller of the currently executing + * generator. + * + * Params: + * value = The value to yield. + */ +void yield(T)(ref T value) +{ + Generator!T cur = cast(Generator!T) Fiber.getThis(); + if (cur !is null && cur.state == Fiber.State.EXEC) + { + cur.m_value = &value; + return Fiber.yield(); + } + throw new Exception("yield(T) called with no active generator for the supplied type", + __FILE__, __LINE__); +} + + +/// ditto +void yield(T)(T value) +{ + yield(value); +} + + +version (Win64) { + // fibers are broken on Win64 +} else version (Win32) { + // fibers are broken in Win32 under server 2012: bug 13821 +} else unittest { + import core.exception; + import std.exception; + + static void testScheduler(Scheduler s) + { + scheduler = s; + scheduler.start( + { + auto tid = spawn( + { + int i; + + try + { + for (i = 1; i < 10; i++) + { + assertNotThrown!AssertError( + assert(receiveOnly!int() == i)); + } + } + catch (OwnerTerminated e) + { + + } + + // i will advance 1 past the last value expected + assert(i == 4); + }); + + auto r = new Generator!int( + { + assertThrown!Exception(yield(2.0)); + yield(); // ensure this is a no-op + yield(1); + yield(); // also once something has been yielded + yield(2); + yield(3); + }); + + foreach (e; r) + { + tid.send(e); + } + }); + scheduler = null; + } + + testScheduler(new ThreadScheduler); + testScheduler(new FiberScheduler); +} + + ////////////////////////////////////////////////////////////////////////////// // MessageBox Implementation ////////////////////////////////////////////////////////////////////////////// @@ -1014,12 +1775,21 @@ private */ class MessageBox { - this() + this() @trusted /* TODO: make @safe after relevant druntime PR gets merged */ { m_lock = new Mutex; - m_putMsg = new Condition( m_lock ); - m_notFull = new Condition( m_lock ); m_closed = false; + + if( scheduler is null ) + { + m_putMsg = new Condition( m_lock ); + m_notFull = new Condition( m_lock ); + } + else + { + m_putMsg = scheduler.newCondition( m_lock ); + m_notFull = scheduler.newCondition( m_lock ); + } } @@ -1129,7 +1899,7 @@ private { alias Ops = TypeTuple!(T[1 .. $]); alias ops = vals[1 .. $]; - assert( vals[0] >= dur!"msecs"(0) ); + assert( vals[0] >= msecs(0) ); enum timedWait = true; Duration period = vals[0]; } @@ -1145,7 +1915,7 @@ private foreach( i, t; Ops ) { alias Args = ParameterTypeTuple!(t); - auto op = ops[i]; + auto op = ops[i]; if( msg.convertsTo!(Args) ) { @@ -1168,11 +1938,12 @@ private assert( msg.convertsTo!(Tid) ); auto tid = msg.get!(Tid); - if( bool* depends = (tid in links) ) + if( bool* pDepends = (tid in thisInfo.links) ) { - links.remove( tid ); + auto depends = *pDepends; + thisInfo.links.remove( tid ); // Give the owner relationship precedence. - if( *depends && tid != owner ) + if( depends && tid != thisInfo.owner ) { auto e = new LinkTerminated( tid ); auto m = Message( MsgType.standard, e ); @@ -1181,9 +1952,9 @@ private throw e; } } - if( tid == owner ) + if( tid == thisInfo.owner ) { - owner = Tid.init; + thisInfo.owner = Tid.init; auto e = new OwnerTerminated( tid ); auto m = Message( MsgType.standard, e ); if( onStandardMsg( m ) ) @@ -1270,7 +2041,7 @@ private static if( timedWait ) { - auto limit = Clock.currTime( UTC() ) + period; + auto limit = Clock.currSystemTick + period; } while( true ) @@ -1282,6 +2053,7 @@ private { return true; } + yield(); synchronized( m_lock ) { updateMsgCount(); @@ -1317,7 +2089,7 @@ private { static if( timedWait ) { - period = limit - Clock.currTime( UTC() ); + period = limit - Clock.currSystemTick; } continue; } @@ -1341,9 +2113,9 @@ private assert( msg.convertsTo!(Tid) ); auto tid = msg.get!(Tid); - links.remove( tid ); - if( tid == owner ) - owner = Tid.init; + thisInfo.links.remove( tid ); + if( tid == thisInfo.owner ) + thisInfo.owner = Tid.init; } void sweep( ref ListT list ) @@ -1447,6 +2219,7 @@ private size_t m_localMsgs; size_t m_maxMsgs; bool m_closed; + } @@ -1500,7 +2273,7 @@ private */ void put( T val ) { - put( new Node( val ) ); + put( newNode( val ) ); } @@ -1546,9 +2319,9 @@ private m_last = null; else if( m_last is n.next ) m_last = n; - Node* todelete = n.next; + Node* to_free = n.next; n.next = n.next.next; - //delete todelete; + freeNode( to_free ); m_count--; } @@ -1593,6 +2366,48 @@ private } } + static shared struct SpinLock + { + void lock() { while (!cas(&locked, false, true)) { Thread.yield(); } } + void unlock() { atomicStore!(MemoryOrder.rel)(locked, false); } + bool locked; + } + static shared SpinLock sm_lock; + static shared Node* sm_head; + + Node* newNode(T v) + { + Node *n; + { + sm_lock.lock(); + scope (exit) sm_lock.unlock(); + + if (sm_head) + { + n = cast(Node*)sm_head; + sm_head = sm_head.next; + } + } + if (n) + *n = Node(v); + else + n = new Node(v); + return n; + } + + void freeNode(Node* n) + { + // destroy val to free any owned GC memory + destroy(n.val); + + sm_lock.lock(); + scope (exit) sm_lock.unlock(); + + auto sn = cast(shared(Node)*)n; + sn.next = sm_head; + sm_head = sn; + } + /* * @@ -1658,7 +2473,7 @@ version( unittest ) } - unittest + void simpleTest() { auto tid = spawn( &testfn, thisTid ); runTest( tid ); @@ -1668,4 +2483,161 @@ version( unittest ) setMaxMailboxSize( tid, 2, OnCrowding.block ); runTest( tid ); } + + + unittest + { + simpleTest(); + } + + + unittest + { + scheduler = new ThreadScheduler; + simpleTest(); + scheduler = null; + } +} + +////////////////////////////////////////////////////////////////////////////// +// initOnce +////////////////////////////////////////////////////////////////////////////// + +private template initOnceLock() +{ + __gshared Mutex lock; + + shared static this() + { + lock = new Mutex; + } + + @property Mutex initOnceLock() + { + return lock; + } +} + +/** + * Initializes $(D_PARAM var) with the lazy $(D_PARAM init) value in a + * thread-safe manner. + * + * The implementation guarantees that all threads simultaneously calling + * initOnce with the same $(D_PARAM var) argument block until $(D_PARAM var) is + * fully initialized. All side-effects of $(D_PARAM init) are globally visible + * afterwards. + * + * Params: + * var = The variable to initialize + * init = The lazy initializer value + * + * Returns: + * A reference to the initialized variable + */ +auto ref initOnce(alias var)(lazy typeof(var) init) +{ + return initOnce!var(init, initOnceLock); +} + +/// A typical use-case is to perform lazy but thread-safe initialization. +unittest +{ + static class MySingleton + { + static MySingleton instance() + { + static __gshared MySingleton inst; + return initOnce!inst(new MySingleton); + } + } + assert(MySingleton.instance !is null); +} + +unittest +{ + static class MySingleton + { + static MySingleton instance() + { + static __gshared MySingleton inst; + return initOnce!inst(new MySingleton); + } + private: + this() { val = ++cnt; } + size_t val; + static __gshared size_t cnt; + } + + foreach (_; 0 .. 10) + spawn({ownerTid.send(MySingleton.instance.val);}); + foreach (_; 0 .. 10) + assert(receiveOnly!size_t == MySingleton.instance.val); + assert(MySingleton.cnt == 1); +} + +/** + * Same as above, but takes a separate mutex instead of sharing one among + * all initOnce instances. + * + * This should be used to avoid dead-locks when the $(D_PARAM init) + * expression waits for the result of another thread that might also + * call initOnce. Use with care. + * + * Params: + * var = The variable to initialize + * init = The lazy initializer value + * mutex = A mutex to prevent race conditions + * + * Returns: + * A reference to the initialized variable + */ +auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex) +{ + // check that var is global, can't take address of a TLS variable + static assert(is(typeof({__gshared p = &var;})), "var must be 'static shared' or '__gshared'."); + import core.atomic; + + static shared bool flag; + if (!atomicLoad!(MemoryOrder.acq)(flag)) + { + synchronized (mutex) + { + if (!atomicLoad!(MemoryOrder.acq)(flag)) + { + var = init; + atomicStore!(MemoryOrder.rel)(flag, true); + } + } + } + return var; +} + +/// Use a separate mutex when init blocks on another thread that might also call initOnce. +unittest +{ + static shared bool varA, varB; + __gshared Mutex m; + m = new Mutex; + + spawn({ + // use a different mutex for varB to avoid a dead-lock + initOnce!varB(true, m); + ownerTid.send(true); + }); + // init depends on the result of the spawned thread + initOnce!varA(receiveOnly!bool); + assert(varA == true); + assert(varB == true); +} + +unittest +{ + static shared bool a; + __gshared bool b; + static bool c; + bool d; + initOnce!a(true); + initOnce!b(true); + static assert(!__traits(compiles, initOnce!c(true))); // TLS + static assert(!__traits(compiles, initOnce!d(true))); // local variable } diff --git a/std/container/array.d b/std/container/array.d index 9ecb5f504a4..6a2df82d964 100644 --- a/std/container/array.d +++ b/std/container/array.d @@ -1,20 +1,212 @@ +/** +This module provides an $(D Array) type with deterministic memory usage not +reliant on the GC, as an alternative to the built-in arrays. + +This module is a submodule of $(LINK2 std_container_package.html, std.container). + +Source: $(PHOBOSSRC std/container/_array.d) +Macros: +WIKI = Phobos/StdContainer +TEXTWITHCOMMAS = $0 + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(WEB +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) +*/ module std.container.array; -import core.exception, core.memory, core.stdc.stdlib, core.stdc.string, - std.algorithm, std.conv, std.exception, std.range, - std.traits, std.typecons; +import std.range.primitives; +import std.traits; +import core.exception : RangeError; +import std.algorithm : move; + public import std.container.util; -version(unittest) import std.stdio; + +private struct RangeT(A) +{ + /* Workaround for Issue 13629 at https://issues.dlang.org/show_bug.cgi?id=13629 + See also: http://forum.dlang.org/thread/vbmwhzvawhnkoxrhbnyb@forum.dlang.org?page=1 + */ + private A[1] _outer_; + private @property ref inout(A) _outer() inout { return _outer_[0]; } + + private size_t _a, _b; + + /* E is different from T when A is more restrictively qualified than T: + immutable(Array!int) => T == int, E = immutable(int) */ + alias E = typeof(_outer_[0]._data._payload[0]); + + private this(ref A data, size_t a, size_t b) + { + _outer_ = data; + _a = a; + _b = b; + } + + @property RangeT save() + { + return this; + } + + @property bool empty() @safe pure nothrow const + { + return _a >= _b; + } + + @property size_t length() @safe pure nothrow const + { + return _b - _a; + } + alias opDollar = length; + + @property ref inout(E) front() inout + { + version (assert) if (empty) throw new RangeError(); + return _outer[_a]; + } + @property ref inout(E) back() inout + { + version (assert) if (empty) throw new RangeError(); + return _outer[_b - 1]; + } + + void popFront() @safe pure nothrow + { + version (assert) if (empty) throw new RangeError(); + ++_a; + } + + void popBack() @safe pure nothrow + { + version (assert) if (empty) throw new RangeError(); + --_b; + } + + static if (isMutable!A) + { + E moveFront() + { + version (assert) if (empty || _a >= _outer.length) throw new RangeError(); + return move(_outer._data._payload[_a]); + } + + E moveBack() + { + version (assert) if (empty || _b > _outer.length) throw new RangeError(); + return move(_outer._data._payload[_b - 1]); + } + + E moveAt(size_t i) + { + version (assert) if (_a + i >= _b || _a + i >= _outer.length) throw new RangeError(); + return move(_outer._data._payload[_a + i]); + } + } + + ref inout(E) opIndex(size_t i) inout + { + version (assert) if (_a + i >= _b) throw new RangeError(); + return _outer[_a + i]; + } + + RangeT opSlice() + { + return typeof(return)(_outer, _a, _b); + } + + RangeT opSlice(size_t i, size_t j) + { + version (assert) if (i > j || _a + j > _b) throw new RangeError(); + return typeof(return)(_outer, _a + i, _a + j); + } + + RangeT!(const(A)) opSlice() const + { + return typeof(return)(_outer, _a, _b); + } + + RangeT!(const(A)) opSlice(size_t i, size_t j) const + { + version (assert) if (i > j || _a + j > _b) throw new RangeError(); + return typeof(return)(_outer, _a + i, _a + j); + } + + static if (isMutable!A) + { + void opSliceAssign(E value) + { + version (assert) if (_b > _outer.length) throw new RangeError(); + _outer[_a .. _b] = value; + } + + void opSliceAssign(E value, size_t i, size_t j) + { + version (assert) if (_a + j > _b) throw new RangeError(); + _outer[_a + i .. _a + j] = value; + } + + void opSliceUnary(string op)() + if (op == "++" || op == "--") + { + version (assert) if (_b > _outer.length) throw new RangeError(); + mixin(op~"_outer[_a .. _b];"); + } + + void opSliceUnary(string op)(size_t i, size_t j) + if (op == "++" || op == "--") + { + version (assert) if (_a + j > _b) throw new RangeError(); + mixin(op~"_outer[_a + i .. _a + j];"); + } + + void opSliceOpAssign(string op)(E value) + { + version (assert) if (_b > _outer.length) throw new RangeError(); + mixin("_outer[_a .. _b] "~op~"= value;"); + } + + void opSliceOpAssign(string op)(E value, size_t i, size_t j) + { + version (assert) if (_a + j > _b) throw new RangeError(); + mixin("_outer[_a + i .. _a + j] "~op~"= value;"); + } + } +} /** Array type with deterministic control of memory. The memory allocated for the array is reclaimed as soon as possible; there is no reliance on the garbage collector. $(D Array) uses $(D malloc) and $(D free) for managing its own memory. + +This means that pointers to elements of an $(D Array) will become +dangling as soon as the element is removed from the $(D Array). On the other hand +the memory allocated by an $(D Array) will be scanned by the GC and +GC managed objects referenced from an $(D Array) will be kept alive. + +Note: + +When using $(D Array) with range-based functions like those in $(D std.algorithm), +$(D Array) must be sliced to get a range (for example, use $(D array[].map!) +instead of $(D array.map!)). The container itself is not a range. */ struct Array(T) if (!is(Unqual!T == bool)) { + import core.stdc.stdlib; + import core.stdc.string; + + import core.memory; + + import std.algorithm : initializeAll, copy; + import std.exception : enforce; + import std.typecons : RefCounted, RefCountedAutoInitialize; + // This structure is not copyable. private struct Payload { @@ -134,6 +326,7 @@ if (!is(Unqual!T == bool)) size_t insertBack(Stuff)(Stuff stuff) if (isImplicitlyConvertible!(Stuff, T)) { + import std.conv : emplace; if (_capacity == length) { reserve(1 + capacity * 3 / 2); @@ -174,11 +367,14 @@ Constructor taking a number of items */ this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) { + import std.conv : emplace; auto p = cast(T*) malloc(T.sizeof * values.length); - if (hasIndirections!T && p) + static if (hasIndirections!T) { - GC.addRange(p, T.sizeof * values.length); + if (p) + GC.addRange(p, T.sizeof * values.length); } + foreach (i, e; values) { emplace(p + i, e); @@ -214,133 +410,14 @@ Comparison for equality. } /** -Defines the container's primary range, which is a random-access range. - */ - static struct Range - { - private Array _outer; - private size_t _a, _b; - - private this(ref Array data, size_t a, size_t b) - { - _outer = data; - _a = a; - _b = b; - } - - @property Range save() - { - return this; - } - - @property bool empty() @safe pure nothrow const - { - return _a >= _b; - } + Defines the container's primary range, which is a random-access range. - @property size_t length() @safe pure nothrow const - { - return _b - _a; - } - alias opDollar = length; - - @property ref T front() - { - version (assert) if (empty) throw new RangeError(); - return _outer[_a]; - } - - @property ref T back() - { - version (assert) if (empty) throw new RangeError(); - return _outer[_b - 1]; - } - - void popFront() @safe pure nothrow - { - version (assert) if (empty) throw new RangeError(); - ++_a; - } - - void popBack() @safe pure nothrow - { - version (assert) if (empty) throw new RangeError(); - --_b; - } - - T moveFront() - { - version (assert) if (empty || _a >= _outer.length) throw new RangeError(); - return move(_outer._data._payload[_a]); - } - - T moveBack() - { - version (assert) if (empty || _b > _outer.length) throw new RangeError(); - return move(_outer._data._payload[_b - 1]); - } - - T moveAt(size_t i) - { - version (assert) if (_a + i >= _b || _a + i >= _outer.length) throw new RangeError(); - return move(_outer._data._payload[_a + i]); - } - - ref T opIndex(size_t i) - { - version (assert) if (_a + i >= _b) throw new RangeError(); - return _outer[_a + i]; - } - - typeof(this) opSlice() - { - return typeof(this)(_outer, _a, _b); - } - - typeof(this) opSlice(size_t i, size_t j) - { - version (assert) if (i > j || _a + j > _b) throw new RangeError(); - return typeof(this)(_outer, _a + i, _a + j); - } - - void opSliceAssign(T value) - { - version (assert) if (_b > _outer.length) throw new RangeError(); - _outer[_a .. _b] = value; - } - - void opSliceAssign(T value, size_t i, size_t j) - { - version (assert) if (_a + j > _b) throw new RangeError(); - _outer[_a + i .. _a + j] = value; - } - - void opSliceUnary(string op)() - if(op == "++" || op == "--") - { - version (assert) if (_b > _outer.length) throw new RangeError(); - mixin(op~"_outer[_a .. _b];"); - } - - void opSliceUnary(string op)(size_t i, size_t j) - if(op == "++" || op == "--") - { - version (assert) if (_a + j > _b) throw new RangeError(); - mixin(op~"_outer[_a + i .. _a + j];"); - } - - void opSliceOpAssign(string op)(T value) - { - version (assert) if (_b > _outer.length) throw new RangeError(); - mixin("_outer[_a .. _b] "~op~"= value;"); - } - - void opSliceOpAssign(string op)(T value, size_t i, size_t j) - { - version (assert) if (_a + j > _b) throw new RangeError(); - mixin("_outer[_a + i .. _a + j] "~op~"= value;"); - } - } + ConstRange is a variant with const elements. + ImmutableRange is a variant with immutable elements. +*/ + alias Range = RangeT!Array; + alias ConstRange = RangeT!(const Array); /// ditto + alias ImmutableRange = RangeT!(immutable Array); /// ditto /** Duplicates the container. The elements themselves are not transitively @@ -427,7 +504,15 @@ Complexity: $(BIGOH 1) */ Range opSlice() { - return Range(this, 0, length); + return typeof(return)(this, 0, length); + } + ConstRange opSlice() const + { + return typeof(return)(this, 0, length); + } + ImmutableRange opSlice() immutable + { + return typeof(return)(this, 0, length); } /** @@ -437,11 +522,21 @@ index $(D a) up to (excluding) index $(D b). Precondition: $(D a <= b && b <= length) Complexity: $(BIGOH 1) - */ +*/ Range opSlice(size_t i, size_t j) { version (assert) if (i > j || j > length) throw new RangeError(); - return Range(this, i, j); + return typeof(return)(this, i, j); + } + ConstRange opSlice(size_t i, size_t j) const + { + version (assert) if (i > j || j > length) throw new RangeError(); + return typeof(return)(this, i, j); + } + ImmutableRange opSlice(size_t i, size_t j) immutable + { + version (assert) if (i > j || j > length) throw new RangeError(); + return typeof(return)(this, i, j); } /** @@ -451,14 +546,14 @@ Precondition: $(D !empty) Complexity: $(BIGOH 1) */ - @property ref T front() + @property ref inout(T) front() inout { version (assert) if (!_data.refCountedStore.isInitialized) throw new RangeError(); return _data._payload[0]; } /// ditto - @property ref T back() + @property ref inout(T) back() inout { version (assert) if (!_data.refCountedStore.isInitialized) throw new RangeError(); return _data._payload[$ - 1]; @@ -471,7 +566,7 @@ Precondition: $(D i < length) Complexity: $(BIGOH 1) */ - ref T opIndex(size_t i) + ref inout(T) opIndex(size_t i) inout { version (assert) if (!_data.refCountedStore.isInitialized) throw new RangeError(); return _data._payload[i]; @@ -501,15 +596,15 @@ Complexity: $(BIGOH slice.length) /// ditto void opSliceUnary(string op)() - if(op == "++" || op == "--") + if (op == "++" || op == "--") { - if(!_data.refCountedStore.isInitialized) return; + if (!_data.refCountedStore.isInitialized) return; mixin(op~"_data._payload[];"); } /// ditto void opSliceUnary(string op)(size_t i, size_t j) - if(op == "++" || op == "--") + if (op == "++" || op == "--") { auto slice = _data.refCountedStore.isInitialized ? _data._payload : T[].init; mixin(op~"slice[i .. j];"); @@ -518,7 +613,7 @@ Complexity: $(BIGOH slice.length) /// ditto void opSliceOpAssign(string op)(T value) { - if(!_data.refCountedStore.isInitialized) return; + if (!_data.refCountedStore.isInitialized) return; mixin("_data._payload[] "~op~"= value;"); } @@ -696,6 +791,7 @@ Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) size_t insertBefore(Stuff)(Range r, Stuff stuff) if (isImplicitlyConvertible!(Stuff, T)) { + import std.conv : emplace; enforce(r._outer._data is _data && r._a <= length); reserve(length + 1); assert(_data.refCountedStore.isInitialized); @@ -712,6 +808,7 @@ Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) size_t insertBefore(Stuff)(Range r, Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) { + import std.conv : emplace; enforce(r._outer._data is _data && r._a <= length); static if (isForwardRange!Stuff) { @@ -736,6 +833,7 @@ Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) } else { + import std.algorithm : bringToFront; enforce(_data); immutable offset = r._a; enforce(offset <= length); @@ -749,6 +847,7 @@ Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) /// ditto size_t insertAfter(Stuff)(Range r, Stuff stuff) { + import std.algorithm : bringToFront; enforce(r._outer._data is _data); // TODO: optimize immutable offset = r._b; @@ -849,6 +948,34 @@ unittest assert(a.length == 3); } +unittest +{ + const Array!int a = [1, 2]; + + assert(a[0] == 1); + assert(a.front == 1); + assert(a.back == 2); + + static assert(!__traits(compiles, { a[0] = 1; })); + static assert(!__traits(compiles, { a.front = 1; })); + static assert(!__traits(compiles, { a.back = 1; })); + + auto r = a[]; + size_t i; + foreach (e; r) + { + assert(e == i + 1); + i++; + } +} + +unittest +{ + // REG https://issues.dlang.org/show_bug.cgi?id=13621 + import std.container : Array, BinaryHeap; + alias Heap = BinaryHeap!(Array!int); +} + unittest { Array!int a; @@ -917,6 +1044,8 @@ unittest // Give the Range object some testing. unittest { + import std.algorithm : equal; + import std.range : retro; auto a = Array!int(0, 1, 2, 3, 4, 5, 6)[]; auto b = Array!int(6, 5, 4, 3, 2, 1, 0)[]; alias A = typeof(a); @@ -988,6 +1117,7 @@ unittest // test replace!Stuff with range Stuff unittest { + import std.algorithm : equal; auto a = Array!int([1, 42, 5]); a.replace(a[1 .. 2], [2, 3, 4]); assert(equal(a[], [1, 2, 3, 4, 5])); @@ -996,24 +1126,28 @@ unittest // test insertBefore and replace with empty Arrays unittest { + import std.algorithm : equal; auto a = Array!int(); a.insertBefore(a[], 1); assert(equal(a[], [1])); } unittest { + import std.algorithm : equal; auto a = Array!int(); a.insertBefore(a[], [1, 2]); assert(equal(a[], [1, 2])); } unittest { + import std.algorithm : equal; auto a = Array!int(); a.replace(a[], [1, 2]); assert(equal(a[], [1, 2])); } unittest { + import std.algorithm : equal; auto a = Array!int(); a.replace(a[], 1); assert(equal(a[], [1])); @@ -1021,6 +1155,7 @@ unittest // make sure that Array instances refuse ranges that don't belong to them unittest { + import std.exception; Array!int a = [1, 2, 3]; auto r = a.dup[]; assertThrown(a.insertBefore(r, 42)); @@ -1063,6 +1198,8 @@ unittest unittest { + import std.algorithm : equal; + //Test "array-wide" operations auto a = Array!int([0, 1, 2]); //Array a[] += 5; @@ -1131,6 +1268,7 @@ unittest //11459 unittest //11884 { + import std.algorithm : filter; auto a = Array!int([1, 2, 2].filter!"true"()); } @@ -1175,6 +1313,41 @@ unittest //6998-2 assert(c.i == 42); //fails } +unittest +{ + static assert(is(Array!int.Range)); + static assert(is(Array!int.ConstRange)); +} + +unittest // const/immutable Array and Ranges +{ + static void test(A, R, E, S)() + { + A a; + R r = a[]; + assert(r.empty); + assert(r.length == 0); + static assert(is(typeof(r.front) == E)); + static assert(is(typeof(r.back) == E)); + static assert(is(typeof(r[0]) == E)); + static assert(is(typeof(r[]) == S)); + static assert(is(typeof(r[0 .. 0]) == S)); + } + + alias A = Array!int; + + test!(A, A.Range, int, A.Range); + test!(A, const A.Range, const int, A.ConstRange); + + test!(const A, A.ConstRange, const int, A.ConstRange); + test!(const A, const A.ConstRange, const int, A.ConstRange); + + test!(immutable A, A.ImmutableRange, immutable int, A.ImmutableRange); + test!(immutable A, const A.ImmutableRange, immutable int, A.ImmutableRange); + test!(immutable A, immutable A.ImmutableRange, immutable int, + A.ImmutableRange); +} + //////////////////////////////////////////////////////////////////////////////// // Array!bool @@ -1187,6 +1360,9 @@ allocating one bit per element. struct Array(T) if (is(Unqual!T == bool)) { + import std.exception : enforce; + import std.typecons : RefCounted, RefCountedAutoInitialize; + static immutable uint bitsPerWord = size_t.sizeof * 8; private static struct Data { @@ -1363,10 +1539,11 @@ if (is(Unqual!T == bool)) unittest { + import std.conv : to; Array!bool a; assert(a.length == 0); a.insert(true); - assert(a.length == 1, text(a.length)); + assert(a.length == 1, to!string(a.length)); } /** @@ -1385,12 +1562,13 @@ if (is(Unqual!T == bool)) unittest { + import std.conv : to; Array!bool a; assert(a.capacity == 0); foreach (i; 0 .. 100) { a.insert(true); - assert(a.capacity >= a.length, text(a.capacity)); + assert(a.capacity >= a.length, to!string(a.capacity)); } } @@ -1404,6 +1582,7 @@ if (is(Unqual!T == bool)) */ void reserve(size_t e) { + import std.conv : to; _store.refCountedStore.ensureInitialized(); _store._backend.reserve(to!size_t((e + bitsPerWord - 1) / bitsPerWord)); } @@ -1580,6 +1759,7 @@ if (is(Unqual!T == bool)) unittest { + import std.algorithm : equal; Array!bool a; a.insertBack([true, false, true, true]); Array!bool b; @@ -1608,6 +1788,7 @@ if (is(Unqual!T == bool)) unittest { + import std.algorithm : equal; Array!bool a; a.insertBack([true, false, true, true]); Array!bool b; @@ -1651,6 +1832,7 @@ if (is(Unqual!T == bool)) */ @property void length(size_t newLength) { + import std.conv : to; _store.refCountedStore.ensureInitialized(); auto newDataLength = to!size_t((newLength + bitsPerWord - 1) / bitsPerWord); @@ -1864,6 +2046,7 @@ if (is(Unqual!T == bool)) */ size_t insertBefore(Stuff)(Range r, Stuff stuff) { + import std.algorithm : bringToFront; // TODO: make this faster, it moves one bit at a time immutable inserted = stableInsertBack(stuff); immutable tailLength = length - inserted; @@ -1877,20 +2060,22 @@ if (is(Unqual!T == bool)) unittest { + import std.conv : to; Array!bool a; version (bugxxxx) { a._store.refCountedDebug = true; } a.insertBefore(a[], true); - assert(a.length == 1, text(a.length)); + assert(a.length == 1, to!string(a.length)); a.insertBefore(a[], false); - assert(a.length == 2, text(a.length)); + assert(a.length == 2, to!string(a.length)); } /// ditto size_t insertAfter(Stuff)(Range r, Stuff stuff) { + import std.algorithm : bringToFront; // TODO: make this faster, it moves one bit at a time immutable inserted = stableInsertBack(stuff); immutable tailLength = length - inserted; @@ -1904,10 +2089,11 @@ if (is(Unqual!T == bool)) unittest { + import std.conv : to; Array!bool a; a.length = 10; a.insertAfter(a[0 .. 5], true); - assert(a.length == 11, text(a.length)); + assert(a.length == 11, to!string(a.length)); assert(a[5]); } /// ditto @@ -1932,10 +2118,11 @@ if (is(Unqual!T == bool)) unittest { + import std.conv : to; Array!bool a; a.length = 10; a.replace(a[3 .. 5], true); - assert(a.length == 9, text(a.length)); + assert(a.length == 9, to!string(a.length)); assert(a[3]); } @@ -1952,6 +2139,7 @@ if (is(Unqual!T == bool)) */ Range linearRemove(Range r) { + import std.algorithm : copy; copy(this[r._b .. length], this[r._a .. length]); length = length - r.length; return this[r._a .. length]; diff --git a/std/container/binaryheap.d b/std/container/binaryheap.d index 78db6d2b45f..1324775b877 100644 --- a/std/container/binaryheap.d +++ b/std/container/binaryheap.d @@ -1,7 +1,28 @@ +/** +This module provides a $(D BinaryHeap) adaptor that makes a binary heap out of +any user-provided random-access range. + +This module is a submodule of $(LINK2 std_container_package.html, std.container). + +Source: $(PHOBOSSRC std/container/_binaryheap.d) +Macros: +WIKI = Phobos/StdContainer +TEXTWITHCOMMAS = $0 + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(WEB +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) +*/ module std.container.binaryheap; -import std.exception, std.algorithm, std.conv, std.range, - std.traits, std.typecons; +import std.range.primitives; +import std.traits; + public import std.container.util; // BinaryHeap @@ -37,6 +58,9 @@ struct BinaryHeap(Store, alias less = "a < b") if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) { import std.functional : binaryFun; + import std.exception : enforce; + import std.algorithm : move, min; + import std.typecons : RefCounted, RefCountedAutoInitialize; // Really weird @@BUG@@: if you comment out the "private:" label below, // std.algorithm can't unittest anymore @@ -68,12 +92,13 @@ if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) { debug { + import std.conv : to; if (!_payload.refCountedStore.isInitialized) return; if (_length < 2) return; for (size_t n = _length - 1; n >= 1; --n) { auto parentIdx = (n - 1) / 2; - assert(!comp(_store[parentIdx], _store[n]), text(n)); + assert(!comp(_store[parentIdx], _store[n]), to!string(n)); } } } @@ -376,6 +401,7 @@ must be collected. /// Example from "Introduction to Algorithms" Cormen et al, p 146 unittest { + import std.algorithm : equal; int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; auto h = heapify(a); // largest element @@ -388,6 +414,8 @@ unittest /// lazy iteration of the underlying range in descending order. unittest { + import std.algorithm : equal; + import std.range : take; int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; auto top5 = heapify(a).take(5); assert(top5.equal([16, 14, 10, 9, 8])); @@ -405,6 +433,7 @@ BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s, unittest { + import std.conv : to; { // example from "Introduction to Algorithms" Cormen et al., p 146 int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; @@ -428,13 +457,14 @@ unittest { h.insert(e); } - assert(b == [ 16, 14, 10, 8, 7, 3, 9, 1, 4, 2 ], text(b)); + assert(b == [ 16, 14, 10, 8, 7, 3, 9, 1, 4, 2 ], to!string(b)); } } unittest { // Test range interface. + import std.algorithm : equal; int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; auto h = heapify(a); static assert(isInputRange!(typeof(h))); diff --git a/std/container/dlist.d b/std/container/dlist.d index fcdf86d59cf..2c5e2e5e07d 100644 --- a/std/container/dlist.d +++ b/std/container/dlist.d @@ -1,8 +1,137 @@ +/** +This module implements a generic doubly-linked list container. + +This module is a submodule of $(LINK2 std_container_package.html, std.container). + +Source: $(PHOBOSSRC std/container/_dlist.d) +Macros: +WIKI = Phobos/StdContainer +TEXTWITHCOMMAS = $0 + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(WEB +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) +*/ module std.container.dlist; -import std.exception, std.range, std.traits; +import std.range.primitives; +import std.traits; + public import std.container.util; +/+ +A DList Node without payload. Used to handle the sentinel node (henceforth "sentinode"). + +Also used for parts of the code that don't depend on the payload type. + +/ +private struct BaseNode +{ + private BaseNode* _prev = null; + private BaseNode* _next = null; + + /+ + Gets the payload associated with this node. + This is trusted because all nodes are associated with a payload, even + the sentinel node. + It is also not possible to mix Nodes in DLists of different types. + This function is implemented as a member function here, as UFCS does not + work with pointers. + +/ + ref inout(T) getPayload(T)() inout @trusted + { + return (cast(inout(DList!T.PayNode)*)&this)._payload; + } + + // Helper: Given nodes p and n, connects them. + static void connect(BaseNode* p, BaseNode* n) @safe nothrow pure + { + p._next = n; + n._prev = p; + } +} + +/+ +The base DList Range. Contains Range primitives that don't depend on payload type. + +/ +private struct DRange +{ + unittest + { + static assert(isBidirectionalRange!DRange); + static assert(is(ElementType!DRange == BaseNode*)); + } + +nothrow @safe pure: + private BaseNode* _first; + private BaseNode* _last; + + private this(BaseNode* first, BaseNode* last) + { + assert((first is null) == (last is null), "Dlist.Range.this: Invalid arguments"); + _first = first; + _last = last; + } + private this(BaseNode* n) + { + this(n, n); + } + + @property + bool empty() const + { + assert((_first is null) == (_last is null), "DList.Range: Invalidated state"); + return !_first; + } + + @property BaseNode* front() + { + assert(!empty, "DList.Range.front: Range is empty"); + return _first; + } + + void popFront() + { + assert(!empty, "DList.Range.popFront: Range is empty"); + if (_first is _last) + { + _first = _last = null; + } + else + { + assert(_first._next && _first is _first._next._prev, "DList.Range: Invalidated state"); + _first = _first._next; + } + } + + @property BaseNode* back() + { + assert(!empty, "DList.Range.front: Range is empty"); + return _last; + } + + void popBack() + { + assert(!empty, "DList.Range.popBack: Range is empty"); + if (_first is _last) + { + _first = _last = null; + } + else + { + assert(_last._prev && _last is _last._prev._next, "DList.Range: Invalidated state"); + _last = _last._prev; + } + } + + /// Forward range primitive. + @property DRange save() { return this; } +} + /** Implements a doubly-linked list. @@ -10,29 +139,53 @@ $(D DList) uses reference semantics. */ struct DList(T) { - private struct Node + import std.range : Take; + + /* + A Node with a Payload. A PayNode. + */ + struct PayNode { + BaseNode _base; + alias _base this; + T _payload = T.init; - Node * _prev; - Node * _next; + + inout(BaseNode)* asBaseNode() inout @trusted + { + return &_base; + } } - private Node* _root; - private void initialize() @safe nothrow pure + + //The sentinel node + private BaseNode* _root; + + private + { + //Construct as new PayNode, and returns it as a BaseNode. + static BaseNode* createNode()(ref T arg, BaseNode* prev = null, BaseNode* next = null) + { + return (new PayNode(BaseNode(prev, next), arg)).asBaseNode(); + } + + void initialize() nothrow @safe pure { if (_root) return; - _root = new Node(); + //Note: We allocate a PayNode for safety reasons. + _root = (new PayNode()).asBaseNode(); _root._next = _root._prev = _root; } - private ref inout(Node*) _first() @property @safe nothrow pure inout + ref inout(BaseNode*) _first() @property @safe nothrow pure inout { assert(_root); return _root._next; } - private ref inout(Node*) _last() @property @safe nothrow pure inout + ref inout(BaseNode*) _last() @property @safe nothrow pure inout { assert(_root); return _root._prev; } + } //end private /** Constructor taking a number of nodes @@ -68,15 +221,15 @@ elements in $(D rhs). if (lroot is null) return rroot is rroot._next; if (rroot is null) return lroot is lroot._next; - const(Node)* pl = lhs._first; - const(Node)* pr = rhs._first; + const(BaseNode)* pl = lhs._first; + const(BaseNode)* pr = rhs._first; while (true) { if (pl is lroot) return pr is rroot; if (pr is rroot) return false; // !== because of NaN - if (!(pl._payload == pr._payload)) return false; + if (!(pl.getPayload!T() == pr.getPayload!T())) return false; pl = pl._next; pr = pr._next; @@ -88,74 +241,33 @@ elements in $(D rhs). */ struct Range { - private Node * _first; - private Node * _last; - private this(Node* first, Node* last) - { - assert(!!_first == !!_last, "Dlist.Range.this: Invalid arguments"); - _first = first; _last = last; - } - private this(Node* n) { _first = _last = n; } + static assert(isBidirectionalRange!Range); - /// Input range primitives. - @property const nothrow - bool empty() - { - assert(!!_first == !!_last, "DList.Range: Invalidated state"); - return !_first; - } + DRange _base; + alias _base this; - /// ditto - @property ref T front() + private this(BaseNode* first, BaseNode* last) { - assert(!empty, "DList.Range.front: Range is empty"); - return _first._payload; + _base = DRange(first, last); } - - /// ditto - void popFront() + private this(BaseNode* n) { - assert(!empty, "DList.Range.popFront: Range is empty"); - if (_first is _last) - { - _first = _last = null; - } - else - { - assert(_first._next && _first is _first._next._prev, "DList.Range: Invalidated state"); - _first = _first._next; - } + this(n, n); } - /// Forward range primitive. - @property Range save() { return this; } - - /// Bidirectional range primitives. - @property ref T back() + @property ref T front() { - assert(!empty, "DList.Range.back: Range is empty"); - return _last._payload; + return _base.front.getPayload!T(); } - /// ditto - void popBack() + @property ref T back() { - assert(!empty, "DList.Range.popBack: Range is empty"); - if (_first is _last) - { - _first = _last = null; - } - else - { - assert(_last._prev && _last is _last._prev._next, "DList.Range: Invalidated state"); - _last = _last._prev; - } + return _base.back.getPayload!T(); } - } - unittest - { - static assert(isBidirectionalRange!Range); + //Note: shadows base DRange.save. + //Necessary for static covariance. + @property Range save() { return this; } } /** @@ -215,7 +327,7 @@ Complexity: $(BIGOH 1) @property ref inout(T) front() inout { assert(!empty, "DList.front: List is empty"); - return _first._payload; + return _first.getPayload!T(); } /** @@ -226,7 +338,7 @@ Complexity: $(BIGOH 1) @property ref inout(T) back() inout { assert(!empty, "DList.back: List is empty"); - return _last._payload; + return _last.getPayload!T(); } /+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ @@ -397,7 +509,7 @@ Complexity: $(BIGOH 1). { assert(!empty, "DList.removeFront: List is empty"); assert(_root is _first._prev, "DList: Inconsistent state"); - connect(_root, _first._next); + BaseNode.connect(_root, _first._next); } /// ditto @@ -408,7 +520,7 @@ Complexity: $(BIGOH 1). { assert(!empty, "DList.removeBack: List is empty"); assert(_last._next is _root, "DList: Inconsistent state"); - connect(_last._prev, _root); + BaseNode.connect(_last._prev, _root); } /// ditto @@ -437,7 +549,7 @@ Complexity: $(BIGOH howMany). p = p._next; ++result; } - connect(_root, p); + BaseNode.connect(_root, p); return result; } @@ -455,7 +567,7 @@ Complexity: $(BIGOH howMany). p = p._prev; ++result; } - connect(p, _root); + BaseNode.connect(p, _root); return result; } @@ -479,7 +591,7 @@ Complexity: $(BIGOH 1) assert(_root !is null, "Cannot remove from an un-initialized List"); assert(r._first, "Remove: Range is empty"); - connect(r._first._prev, r._last._next); + BaseNode.connect(r._first._prev, r._last._next); auto after = r._last._next; if (after is _root) return Range(null, null); @@ -505,8 +617,8 @@ Complexity: $(BIGOH r.walkLength) assert(_root !is null, "Cannot remove from an un-initialized List"); assert(r.source._first, "Remove: Range is empty"); - Node* first = r.source._first; - Node* last = void; + BaseNode* first = r.source._first; + BaseNode* last = null; do { last = r.source._first; @@ -522,64 +634,58 @@ Complexity: $(BIGOH r.walkLength) alias stableLinearRemove = linearRemove; private: - // Helper: Given nodes p and n, connects them. - void connect(Node* p, Node* n) @trusted nothrow pure - { - p._next = n; - n._prev = p; - } // Helper: Inserts stuff before the node n. - size_t insertBeforeNode(Stuff)(Node* n, ref Stuff stuff) + size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff) if (isImplicitlyConvertible!(Stuff, T)) { - auto p = new Node(stuff, n._prev, n); + auto p = createNode(stuff, n._prev, n); n._prev._next = p; n._prev = p; return 1; } // ditto - size_t insertBeforeNode(Stuff)(Node* n, ref Stuff stuff) + size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) { if (stuff.empty) return 0; size_t result; Range r = createRange(stuff, result); - connect(n._prev, r._first); - connect(r._last, n); + BaseNode.connect(n._prev, r._first); + BaseNode.connect(r._last, n); return result; } // Helper: Inserts stuff after the node n. - size_t insertAfterNode(Stuff)(Node* n, ref Stuff stuff) + size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff) if (isImplicitlyConvertible!(Stuff, T)) { - auto p = new Node(stuff, n, n._next); + auto p = createNode(stuff, n, n._next); n._next._prev = p; n._next = p; return 1; } // ditto - size_t insertAfterNode(Stuff)(Node* n, ref Stuff stuff) + size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) { if (stuff.empty) return 0; size_t result; Range r = createRange(stuff, result); - connect(r._last, n._next); - connect(n, r._first); + BaseNode.connect(r._last, n._next); + BaseNode.connect(n, r._first); return result; } // Helper: Creates a chain of nodes from the range stuff. Range createRange(Stuff)(ref Stuff stuff, ref size_t result) { - Node* first = new Node(stuff.front); - Node* last = first; + BaseNode* first = createNode(stuff.front); + BaseNode* last = first; ++result; for ( stuff.popFront() ; !stuff.empty ; stuff.popFront() ) { - auto p = new Node(stuff.front, last); + auto p = createNode(stuff.front, last); last = last._next = p; ++result; } @@ -587,7 +693,7 @@ private: } } -unittest +@safe unittest { import std.algorithm : equal; @@ -606,7 +712,7 @@ unittest assert(equal(a4[], [0, 1])); } -unittest +@safe unittest { import std.algorithm : equal; @@ -623,9 +729,10 @@ unittest assert(equal(list[],[4,5,6,7,0,1,2,3])); } -unittest +@safe unittest { import std.algorithm : equal; + import std.range : take; alias IntList = DList!int; IntList list = IntList([0,1,2,3]); @@ -681,7 +788,7 @@ unittest assert(equal(list[],[0,3])); } -unittest +@safe unittest { import std.algorithm : equal; @@ -694,7 +801,7 @@ unittest assert(equal(dl[], ["a", "b", "c", "d", "e"])); } -unittest +@safe unittest { import std.algorithm : equal; @@ -707,7 +814,7 @@ unittest assert(equal(dl[], ["e", "a", "c", "b", "d"])); } -unittest +@safe unittest { auto d = DList!int([1, 2, 3]); d.front = 5; //test frontAssign @@ -718,7 +825,7 @@ unittest } // Issue 8895 -unittest +@safe unittest { auto a = make!(DList!int)(1,2,3,4); auto b = make!(DList!int)(1,2,3,4); @@ -729,7 +836,7 @@ unittest assert(!(a == d)); } -unittest +@safe unittest { auto d = DList!int([1, 2, 3]); d.front = 5; //test frontAssign @@ -739,7 +846,7 @@ unittest assert(r.back == 1); } -unittest +@safe unittest { auto a = DList!int(); assert(a.removeFront(10) == 0); @@ -748,7 +855,7 @@ unittest assert(a[].empty); } -unittest +@safe unittest { import std.algorithm : equal; @@ -784,7 +891,7 @@ unittest c.removeBack(); } -unittest +@safe unittest { import std.algorithm : equal; @@ -796,7 +903,7 @@ unittest assert(a[].equal([1, 2, 3, 7])); } -unittest //12566 +@safe unittest //12566 { auto dl2 = DList!int([2,7]); dl2.removeFront(); @@ -805,15 +912,16 @@ unittest //12566 assert(dl2.empty, "not empty?!"); } -unittest //13076 +@safe unittest //13076 { DList!int list; assert(list.empty); list.clear(); } -unittest //13425 +@safe unittest //13425 { + import std.range : drop, take; auto list = DList!int([1,2,3,4,5]); auto r = list[].drop(4); // r is a view of the last element of list assert(r.front == 5 && r.walkLength == 1); diff --git a/std/container/package.d b/std/container/package.d index c14bfd4b055..11e305f33c9 100644 --- a/std/container/package.d +++ b/std/container/package.d @@ -1,45 +1,235 @@ // Written in the D programming language. /** -Defines generic containers. +This module defines generic containers. + +Construction: + +To implement the different containers both struct and class based +approaches have been used. $(XREF container_util, make) allows for +uniform construction with either approach. + +--- +import std.container; +// Construct a red-black tree and an array both containing the values 1, 2, 3. +// RedBlackTree should typically be allocated using `new` +RedBlackTree!int rbTree = new RedBlackTree!int(1, 2, 3); +// But `new` should not be used with Array +Array!int array = Array!int(1, 2, 3); +// `make` hides the differences +RedBlackTree!int rbTree2 = make!(RedBlackTree!int)(1, 2, 3); +Array!int array2 = make!(Array!int)(1, 2, 3); +--- + +Note that $(D make) can infer the element type from the given arguments. + +--- +import std.container; +auto rbTree = make!RedBlackTree(1, 2, 3); // RedBlackTree!int +auto array = make!Array("1", "2", "3"); // Array!string +--- + +Reference_semantics: + +All containers have reference semantics, which means that after +assignment both variables refer to the same underlying data. + +To make a copy of a _container, use the $(D c._dup) _container primitive. +--- +import std.container, std.range; +Array!int originalArray = make!(Array!int)(1, 2, 3); +Array!int secondArray = originalArray; +assert(equal(originalArray[], secondArray[])); + +// changing one instance changes the other one as well! +originalArray[0] = 12; +assert(secondArray[0] == 12); + +// secondArray now refers to an independent copy of originalArray +secondArray = originalArray.dup; +secondArray[0] = 1; +// assert that originalArray has not been effected +assert(originalArray[0] == 12); +--- + +$(B Attention:) If the _container is implemented as a class, using an +uninitialized instance can cause a null pointer dereference. + +--- +import std.container; + +RedBlackTree!int rbTree; +rbTree.insert(5); // null pointer dereference +--- + +Using an uninitialized struct-based _container will work, because the struct +intializes itself upon use; however, up to this point the _container will not +have an identity and assignment does not create two references to the same +data. + +--- +import std.container; + +// create an uninitialized array +Array!int array1; +// array2 does _not_ refer to array1 +Array!int array2 = array1; +array2.insertBack(42); +// thus array1 will not be affected +assert(array1.empty); + +// after initialization reference semantics work as expected +array1 = array2; +// now effects array2 as well +array1.removeBack(); +assert(array2.empty); +--- +It is therefore recommended to always construct containers using $(XREF container_util, make). + +This is in fact necessary to put containers into another _container. +For example, to construct an $(D Array) of ten empty $(D Array)s, use +the following that calls $(D make) ten times. + +--- +import std.range, std.container, std.algorithm; + +Array!(Array!int) arrayOfArrays = make!(Array!(Array!int))( + repeat(0, 10).map!(x => make!(Array!int)) +); +--- + +Submodules: + +This module consists of the following submodules: + +$(UL + $(LI + The $(LINK2 std_container_array.html, std._container.array) module provides + an array type with deterministic control of memory, not reliant on + the GC unlike built-in arrays. + ) + $(LI + The $(LINK2 std_container_binaryheap.html, std._container.binaryheap) module + provides a binary heap implementation that can be applied to any + user-provided random-access range. + ) + $(LI + The $(LINK2 std_container_dlist.html, std._container.dlist) module provides + a doubly-linked list implementation. + ) + $(LI + The $(LINK2 std_container_rbtree.html, std._container.rbtree) module + implements red-black trees. + ) + $(LI + The $(LINK2 std_container_slist.html, std._container.slist) module + implements singly-linked lists. + ) + $(LI + The $(LINK2 std_container_util.html, std._container.util) module contains + some generic tools commonly used by _container implementations. + ) +) -Source: $(PHOBOSSRC std/container/_package.d) -Macros: -WIKI = Phobos/StdContainer -TEXTWITHCOMMAS = $0 +The_primary_range_of_a_container: -Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code -copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. +While some _containers offer direct access to their elements e.g. via +$(D opIndex), $(D c.front) or $(D c.back), access +and modification of a _container's contents is generally done through +its primary $(LINK2 std_range_package.html, range) type, +which is aliased as $(D C.Range). For example, the primary range type of +$(D Array!int) is $(D Array!int.Range). -License: Distributed under the Boost Software License, Version 1.0. -(See accompanying file LICENSE_1_0.txt or copy at $(WEB -boost.org/LICENSE_1_0.txt)). +If the documentation of a member function of a _container takes a +a parameter of type $(D Range), then it refers to the primary range type of +this _container. Oftentimes $(D Take!Range) will be used, in which case +the range refers to a span of the elements in the _container. Arguments to +these parameters $(B must) be obtained from the same _container instance +as the one being worked with. It is important to note that many generic range +algorithms return the same range type as their input range. -Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) +--- +import std.algorithm : equal, find; +import std.container; +import std.range : takeOne; + +auto array = make!Array(1, 2, 3); + +// `find` returns an Array!int.Range advanced to the element "2" +array.linearRemove(array[].find(2)); + +assert(array[].equal([1])); + +array = make!Array(1, 2, 3); + +// the range given to `linearRemove` is a Take!(Array!int.Range) +// spanning just the element "2" +array.linearRemove(array[].find(2).takeOne()); + +assert(array[].equal([1, 3])); +--- + +When any $(LINK2 std_range_package.html, range) can be passed as an argument to +a member function, the documention usually refers to the parameter's templated +type as $(D Stuff). + +--- +import std.algorithm : equal; +import std.container; +import std.range : iota; + +auto array = make!Array(1, 2); + +// the range type returned by `iota` is completely unrelated to Array, +// which is fine for Array.insertBack: +array.insertBack(iota(3, 10)); -$(BOOKTABLE $(TEXTWITHCOMMAS Container primitives. A _container need not -implement all primitives, but if a primitive is implemented, it must -support the syntax described in the $(B syntax) column with the semantics -described in the $(B description) column, and it must not have worse worst-case -complexity than denoted in big-O notation in the $(BIGOH ·) column. -Below, $(D C) means a _container type, $(D c) is a value of _container -type, $(D n$(SUBx)) represents the effective length of value $(D x), -which could be a single element (in which case $(D n$(SUB x)) is $(D 1)), -a _container, or a range.), +assert(array[].equal([1, 2, 3, 4, 5, 6, 7, 8, 9])); +--- + +Container_primitives: + +Containers do not form a class hierarchy, instead they implement a +common set of primitives (see table below). These primitives each guarantee +a specific worst case complexity and thus allow generic code to be written +independently of the _container implementation. + +For example the primitives $(D c.remove(r)) and $(D c.linearRemove(r)) both +remove the sequence of elements in range $(D r) from the _container $(D c). +The primitive $(D c.remove(r)) guarantees $(BIGOH 1) complexity and +$(D c.linearRemove(r)) relaxes this guarantee to $(BIGOH n) (where $(D n) +is the length of the _container $(D c)). + +Since a sequence of elements can be removed from a $(LINK2 std_container_dlist.html, doubly linked list) +in constant time, $(D DList) provides the primitive $(D c.remove(r)) +as well as $(D c.linearRemove(r)). On the other hand a +$(LINK2 std_container_array.html, Array) only offers $(D c.linearRemove(r)). + +The following table describes the common set of primitives that containers +implement. A _container need not implement all primitives, but if a +primitive is implemented, it must support the syntax described in the $(B +syntax) column with the semantics described in the $(B description) column, and +it must not have a worst-case complexity worse than denoted in big-O notation in +the $(BIGOH ·) column. Below, $(D C) means a _container type, $(D c) is +a value of _container type, $(D n$(SUBSCRIPT x)) represents the effective length of +value $(D x), which could be a single element (in which case $(D n$(SUBSCRIPT x)) is +$(D 1)), a _container, or a range. + +$(BOOKTABLE Container primitives, $(TR $(TH Syntax) $(TH $(BIGOH ·)) $(TH Description)) -$(TR $(TDNW $(D C(x))) $(TDNW $(D n$(SUB x))) $(TD Creates a -_container of type $(D C) from either another _container or a range.)) +$(TR $(TDNW $(D C(x))) $(TDNW $(D n$(SUBSCRIPT x))) $(TD Creates a +_container of type $(D C) from either another _container or a range. The created _container must not be a null reference even if x is empty.)) -$(TR $(TDNW $(D c.dup)) $(TDNW $(D n$(SUB c))) $(TD Returns a +$(TR $(TDNW $(D c.dup)) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Returns a duplicate of the _container.)) -$(TR $(TDNW $(D c ~ x)) $(TDNW $(D n$(SUB c) + n$(SUB x))) $(TD +$(TR $(TDNW $(D c ~ x)) $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) $(TD Returns the concatenation of $(D c) and $(D r). $(D x) may be a single element or an input range.)) -$(TR $(TDNW $(D x ~ c)) $(TDNW $(D n$(SUB c) + n$(SUB x))) $(TD +$(TR $(TDNW $(D x ~ c)) $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) $(TD Returns the concatenation of $(D x) and $(D c). $(D x) may be a single element or an input range type.)) @@ -48,10 +238,10 @@ $(LEADINGROW Iteration) $(TR $(TD $(D c.Range)) $(TD) $(TD The primary range type associated with the _container.)) -$(TR $(TD $(D c[])) $(TDNW $(D log n$(SUB c))) $(TD Returns a range +$(TR $(TD $(D c[])) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns a range iterating over the entire _container, in a _container-defined order.)) -$(TR $(TDNW $(D c[a .. b])) $(TDNW $(D log n$(SUB c))) $(TD Fetches a +$(TR $(TDNW $(D c[a .. b])) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Fetches a portion of the _container from key $(D a) to key $(D b).)) $(LEADINGROW Capacity) @@ -59,157 +249,171 @@ $(LEADINGROW Capacity) $(TR $(TD $(D c.empty)) $(TD $(D 1)) $(TD Returns $(D true) if the _container has no elements, $(D false) otherwise.)) -$(TR $(TD $(D c.length)) $(TDNW $(D log n$(SUB c))) $(TD Returns the +$(TR $(TD $(D c.length)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the number of elements in the _container.)) -$(TR $(TDNW $(D c.length = n)) $(TDNW $(D n$(SUB c) + n)) $(TD Forces +$(TR $(TDNW $(D c.length = n)) $(TDNW $(D n$(SUBSCRIPT c) + n)) $(TD Forces the number of elements in the _container to $(D n). If the _container ends up growing, the added elements are initialized in a _container-dependent manner (usually with $(D T.init)).)) -$(TR $(TD $(D c.capacity)) $(TDNW $(D log n$(SUB c))) $(TD Returns the +$(TR $(TD $(D c.capacity)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the maximum number of elements that can be stored in the _container without triggering a reallocation.)) -$(TR $(TD $(D c.reserve(x))) $(TD $(D n$(SUB c))) $(TD Forces $(D +$(TR $(TD $(D c.reserve(x))) $(TD $(D n$(SUBSCRIPT c))) $(TD Forces $(D capacity) to at least $(D x) without reducing it.)) $(LEADINGROW Access) -$(TR $(TDNW $(D c.front)) $(TDNW $(D log n$(SUB c))) $(TD Returns the +$(TR $(TDNW $(D c.front)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the first element of the _container, in a _container-defined order.)) -$(TR $(TDNW $(D c.moveFront)) $(TDNW $(D log n$(SUB c))) $(TD +$(TR $(TDNW $(D c.moveFront)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Destructively reads and returns the first element of the _container. The slot is not removed from the _container; it is left initialized with $(D T.init). This routine need not be defined if $(D front) returns a $(D ref).)) -$(TR $(TDNW $(D c.front = v)) $(TDNW $(D log n$(SUB c))) $(TD Assigns +$(TR $(TDNW $(D c.front = v)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Assigns $(D v) to the first element of the _container.)) -$(TR $(TDNW $(D c.back)) $(TDNW $(D log n$(SUB c))) $(TD Returns the +$(TR $(TDNW $(D c.back)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns the last element of the _container, in a _container-defined order.)) -$(TR $(TDNW $(D c.moveBack)) $(TDNW $(D log n$(SUB c))) $(TD +$(TR $(TDNW $(D c.moveBack)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Destructively reads and returns the last element of the -container. The slot is not removed from the _container; it is left +_container. The slot is not removed from the _container; it is left initialized with $(D T.init). This routine need not be defined if $(D front) returns a $(D ref).)) -$(TR $(TDNW $(D c.back = v)) $(TDNW $(D log n$(SUB c))) $(TD Assigns +$(TR $(TDNW $(D c.back = v)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Assigns $(D v) to the last element of the _container.)) -$(TR $(TDNW $(D c[x])) $(TDNW $(D log n$(SUB c))) $(TD Provides +$(TR $(TDNW $(D c[x])) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Provides indexed access into the _container. The index type is -_container-defined. A container may define several index types (and +_container-defined. A _container may define several index types (and consequently overloaded indexing).)) -$(TR $(TDNW $(D c.moveAt(x))) $(TDNW $(D log n$(SUB c))) $(TD +$(TR $(TDNW $(D c.moveAt(x))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Destructively reads and returns the value at position $(D x). The slot is not removed from the _container; it is left initialized with $(D T.init).)) -$(TR $(TDNW $(D c[x] = v)) $(TDNW $(D log n$(SUB c))) $(TD Sets +$(TR $(TDNW $(D c[x] = v)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Sets element at specified index into the _container.)) -$(TR $(TDNW $(D c[x] $(I op)= v)) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c[x] $(I op)= v)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Performs read-modify-write operation at specified index into the _container.)) $(LEADINGROW Operations) -$(TR $(TDNW $(D e in c)) $(TDNW $(D log n$(SUB c))) $(TD +$(TR $(TDNW $(D e in c)) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns nonzero if e is found in $(D c).)) -$(TR $(TDNW $(D c.lowerBound(v))) $(TDNW $(D log n$(SUB c))) $(TD +$(TR $(TDNW $(D c.lowerBound(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns a range of all elements strictly less than $(D v).)) -$(TR $(TDNW $(D c.upperBound(v))) $(TDNW $(D log n$(SUB c))) $(TD +$(TR $(TDNW $(D c.upperBound(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns a range of all elements strictly greater than $(D v).)) -$(TR $(TDNW $(D c.equalRange(v))) $(TDNW $(D log n$(SUB c))) $(TD +$(TR $(TDNW $(D c.equalRange(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns a range of all elements in $(D c) that are equal to $(D v).)) $(LEADINGROW Modifiers) -$(TR $(TDNW $(D c ~= x)) $(TDNW $(D n$(SUB c) + n$(SUB x))) +$(TR $(TDNW $(D c ~= x)) $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) $(TD Appends $(D x) to $(D c). $(D x) may be a single element or an input range type.)) -$(TR $(TDNW $(D c.clear())) $(TDNW $(D n$(SUB c))) $(TD Removes all +$(TR $(TDNW $(D c.clear())) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Removes all elements in $(D c).)) -$(TR $(TDNW $(D c.insert(x))) $(TDNW $(D n$(SUB x) * log n$(SUB c))) +$(TR $(TDNW $(D c.insert(x))) $(TDNW $(D n$(SUBSCRIPT x) * log n$(SUBSCRIPT c))) $(TD Inserts $(D x) in $(D c) at a position (or positions) chosen by $(D c).)) $(TR $(TDNW $(D c.stableInsert(x))) -$(TDNW $(D n$(SUB x) * log n$(SUB c))) $(TD Same as $(D c.insert(x)), +$(TDNW $(D n$(SUBSCRIPT x) * log n$(SUBSCRIPT c))) $(TD Same as $(D c.insert(x)), but is guaranteed to not invalidate any ranges.)) -$(TR $(TDNW $(D c.linearInsert(v))) $(TDNW $(D n$(SUB c))) $(TD Same +$(TR $(TDNW $(D c.linearInsert(v))) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Same as $(D c.insert(v)) but relaxes complexity to linear.)) -$(TR $(TDNW $(D c.stableLinearInsert(v))) $(TDNW $(D n$(SUB c))) +$(TR $(TDNW $(D c.stableLinearInsert(v))) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Same as $(D c.stableInsert(v)) but relaxes complexity to linear.)) -$(TR $(TDNW $(D c.removeAny())) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.removeAny())) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Removes some element from $(D c) and returns it.)) -$(TR $(TDNW $(D c.stableRemoveAny())) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.stableRemoveAny())) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Same as $(D c.removeAny()), but is guaranteed to not invalidate any iterators.)) -$(TR $(TDNW $(D c.insertFront(v))) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.insertFront(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Inserts $(D v) at the front of $(D c).)) -$(TR $(TDNW $(D c.stableInsertFront(v))) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.stableInsertFront(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Same as $(D c.insertFront(v)), but guarantees no ranges will be invalidated.)) -$(TR $(TDNW $(D c.insertBack(v))) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.insertBack(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Inserts $(D v) at the back of $(D c).)) -$(TR $(TDNW $(D c.stableInsertBack(v))) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.stableInsertBack(v))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Same as $(D c.insertBack(v)), but guarantees no ranges will be invalidated.)) -$(TR $(TDNW $(D c.removeFront())) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.removeFront())) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Removes the element at the front of $(D c).)) -$(TR $(TDNW $(D c.stableRemoveFront())) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.stableRemoveFront())) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Same as $(D c.removeFront()), but guarantees no ranges will be invalidated.)) -$(TR $(TDNW $(D c.removeBack())) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.removeBack())) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Removes the value at the back of $(D c).)) -$(TR $(TDNW $(D c.stableRemoveBack())) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.stableRemoveBack())) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Same as $(D c.removeBack()), but guarantees no ranges will be invalidated.)) -$(TR $(TDNW $(D c.remove(r))) $(TDNW $(D n$(SUB r) * log n$(SUB c))) +$(TR $(TDNW $(D c.remove(r))) $(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c))) $(TD Removes range $(D r) from $(D c).)) $(TR $(TDNW $(D c.stableRemove(r))) -$(TDNW $(D n$(SUB r) * log n$(SUB c))) +$(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c))) $(TD Same as $(D c.remove(r)), but guarantees iterators are not invalidated.)) -$(TR $(TDNW $(D c.linearRemove(r))) $(TDNW $(D n$(SUB c))) +$(TR $(TDNW $(D c.linearRemove(r))) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Removes range $(D r) from $(D c).)) -$(TR $(TDNW $(D c.stableLinearRemove(r))) $(TDNW $(D n$(SUB c))) +$(TR $(TDNW $(D c.stableLinearRemove(r))) $(TDNW $(D n$(SUBSCRIPT c))) $(TD Same as $(D c.linearRemove(r)), but guarantees iterators are not invalidated.)) -$(TR $(TDNW $(D c.removeKey(k))) $(TDNW $(D log n$(SUB c))) +$(TR $(TDNW $(D c.removeKey(k))) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Removes an element from $(D c) by using its key $(D k). The key's type is defined by the _container.)) $(TR $(TDNW $(D )) $(TDNW $(D )) $(TD )) ) + +Source: $(PHOBOSSRC std/_container/package.d) +Macros: +WIKI = Phobos/StdContainer +TEXTWITHCOMMAS = $0 + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(WEB +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) */ module std.container; diff --git a/std/container/rbtree.d b/std/container/rbtree.d index e4821614501..1854e189d55 100644 --- a/std/container/rbtree.d +++ b/std/container/rbtree.d @@ -1,18 +1,32 @@ +/** +This module implements a red-black tree container. + +This module is a submodule of $(LINK2 std_container_package.html, std.container). + +Source: $(PHOBOSSRC std/container/_rbtree.d) +Macros: +WIKI = Phobos/StdContainer +TEXTWITHCOMMAS = $0 + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(WEB +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) +*/ module std.container.rbtree; -import std.exception, std.algorithm, std.range, std.traits; -import std.functional : binaryFun; -import std.typetuple : allSatisfy; +// FIXME +import std.functional; // : binaryFun; + public import std.container.util; -version(unittest) version = RBDoChecks; +version(unittest) debug = RBDoChecks; -//version = RBDoChecks; - -version(RBDoChecks) -{ - import std.stdio; -} +//debug = RBDoChecks; /* * Implementation for a Red Black node for use in a Red Black Tree (see below) @@ -491,6 +505,9 @@ struct RBNode(V) _parent.right = null; } + // clean references to help GC - Bugzilla 12915 + _left = _right = _parent = null; + return ret; } @@ -607,6 +624,11 @@ struct RBNode(V) final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) if(is(typeof(binaryFun!less(T.init, T.init)))) { + import std.range.primitives; + import std.range : Take; + import std.typetuple : allSatisfy; + import std.traits; + alias _less = binaryFun!less; // BUG: this must come first in the struct due to issue 2810 @@ -672,7 +694,7 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) static if(allowDuplicates) { result.setColor(_end); - version(RBDoChecks) + debug(RBDoChecks) check(); ++_length; return result; @@ -686,7 +708,7 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) ++_length; result.setColor(_end); } - version(RBDoChecks) + debug(RBDoChecks) check(); return Tuple!(bool, "added", Node, "n")(added, result); } @@ -694,7 +716,12 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) version(unittest) { - private enum doUnittest = isIntegral!T; + static if(is(typeof(less) == string)) + { + private enum doUnittest = isIntegral!T && (less == "a < b" || less == "a > b"); + } + else + enum doUnittest = false; // note, this must be final so it does not affect the vtable layout final bool arrayEqual(T[] arr) @@ -815,6 +842,8 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) static if(doUnittest) unittest { + import std.algorithm : equal; + import std.range.primitives; auto ts = new RedBlackTree(1, 2, 3, 4, 5); assert(ts.length == 5); auto r = ts[]; @@ -823,8 +852,8 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) auto vals = [1, 2, 3, 4, 5]; else auto vals = [5, 4, 3, 2, 1]; + assert(equal(r, vals)); - assert(std.algorithm.equal(r, vals)); assert(r.front == vals.front); assert(r.back != r.front); auto oldfront = r.front; @@ -907,13 +936,14 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) static if(doUnittest) unittest { + import std.algorithm : equal; auto ts = new RedBlackTree(1, 2, 3, 4, 5); assert(ts.length == 5); auto ts2 = ts.dup; assert(ts2.length == 5); - assert(std.algorithm.equal(ts[], ts2[])); + assert(equal(ts[], ts2[])); ts2.insert(cast(Elem)6); - assert(!std.algorithm.equal(ts[], ts2[])); + assert(!equal(ts[], ts2[])); assert(ts.length == 5 && ts2.length == 6); } @@ -972,6 +1002,7 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) */ override bool opEquals(Object rhs) { + import std.algorithm : equal; RedBlackTree that = cast(RedBlackTree)rhs; if (that is null) return false; @@ -1112,7 +1143,7 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) auto n = _begin; auto result = n.value; _begin = n.remove(_end); - version(RBDoChecks) + debug(RBDoChecks) check(); return result; } @@ -1139,7 +1170,7 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) scope(success) --_length; _begin = _begin.remove(_end); - version(RBDoChecks) + debug(RBDoChecks) check(); } @@ -1157,7 +1188,7 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) _begin = _begin.remove(_end); else lastnode.remove(_end); - version(RBDoChecks) + debug(RBDoChecks) check(); } @@ -1197,13 +1228,14 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) b = b.remove(_end); --_length; } - version(RBDoChecks) + debug(RBDoChecks) check(); return Range(e, _end); } static if(doUnittest) unittest { + import std.algorithm : equal; auto ts = new RedBlackTree(1,2,3,4,5); assert(ts.length == 5); auto r = ts[]; @@ -1215,9 +1247,9 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) assert(ts.arrayEqual([1,5])); static if(less == "a < b") - assert(std.algorithm.equal(r2, [5])); + assert(equal(r2, [5])); else - assert(std.algorithm.equal(r2, [1])); + assert(equal(r2, [1])); } /++ @@ -1249,6 +1281,8 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) static if(doUnittest) unittest { + import std.algorithm : equal; + import std.range : take; auto ts = new RedBlackTree(1,2,3,4,5); auto r = ts[]; r.popFront(); @@ -1257,17 +1291,17 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) static if(less == "a < b") { - assert(std.algorithm.equal(r2, [2,3,4,5])); + assert(equal(r2, [2,3,4,5])); auto r3 = ts.remove(take(r, 2)); assert(ts.arrayEqual([1,4,5]) && ts.length == 3); - assert(std.algorithm.equal(r3, [4,5])); + assert(equal(r3, [4,5])); } else { - assert(std.algorithm.equal(r2, [4,3,2,1])); + assert(equal(r2, [4,3,2,1])); auto r3 = ts.remove(take(r, 2)); assert(ts.arrayEqual([5,2,1]) && ts.length == 3); - assert(std.algorithm.equal(r3, [2,1])); + assert(equal(r3, [2,1])); } } @@ -1285,9 +1319,9 @@ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) -------------------- auto rbt = redBlackTree!true(0, 1, 1, 1, 4, 5, 7); rbt.removeKey(1, 4, 7); -assert(std.algorithm.equal(rbt[], [0, 1, 1, 5])); +assert(equal(rbt[], [0, 1, 1, 5])); rbt.removeKey(1, 1, 0); -assert(std.algorithm.equal(rbt[], [5])); +assert(equal(rbt[], [5])); -------------------- +/ size_t removeKey(U...)(U elems) @@ -1329,6 +1363,7 @@ assert(std.algorithm.equal(rbt[], [5])); isImplicitlyConvertible!(ElementType!Stuff, Elem) && !isDynamicArray!Stuff) { + import std.array : array; //We use array in case stuff is a Range from this RedBlackTree - either //directly or indirectly. return removeKey(array(stuff)); @@ -1342,6 +1377,8 @@ assert(std.algorithm.equal(rbt[], [5])); static if(doUnittest) unittest { + import std.algorithm : equal; + import std.range : take; auto rbt = new RedBlackTree(5, 4, 3, 7, 2, 1, 7, 6, 2, 19, 45); //The cast(Elem) is because these tests are instantiated with a variety @@ -1360,9 +1397,9 @@ assert(std.algorithm.equal(rbt[], [5])); assert(rbt.removeKey(take(rbt[], 3)) == 3 && rbt.length == 4); static if(less == "a < b") - assert(std.algorithm.equal(rbt[], [7,7,19,45])); + assert(equal(rbt[], [7,7,19,45])); else - assert(std.algorithm.equal(rbt[], [7,5,3,2])); + assert(equal(rbt[], [7,5,3,2])); } else { @@ -1377,9 +1414,9 @@ assert(std.algorithm.equal(rbt[], [5])); assert(rbt.removeKey(take(rbt[], 3)) == 3 && rbt.length == 2); static if(less == "a < b") - assert(std.algorithm.equal(rbt[], [19,45])); + assert(equal(rbt[], [19,45])); else - assert(std.algorithm.equal(rbt[], [5,3])); + assert(equal(rbt[], [5,3])); } } @@ -1470,6 +1507,7 @@ assert(std.algorithm.equal(rbt[], [5])); static if(doUnittest) unittest { + import std.algorithm : equal; auto ts = new RedBlackTree(1, 2, 3, 4, 5); auto rl = ts.lowerBound(3); auto ru = ts.upperBound(3); @@ -1477,19 +1515,19 @@ assert(std.algorithm.equal(rbt[], [5])); static if(less == "a < b") { - assert(std.algorithm.equal(rl, [1,2])); - assert(std.algorithm.equal(ru, [4,5])); + assert(equal(rl, [1,2])); + assert(equal(ru, [4,5])); } else { - assert(std.algorithm.equal(rl, [5,4])); - assert(std.algorithm.equal(ru, [2,1])); + assert(equal(rl, [5,4])); + assert(equal(ru, [2,1])); } - assert(std.algorithm.equal(re, [3])); + assert(equal(re, [3])); } - version(RBDoChecks) + debug(RBDoChecks) { /* * Print the tree. This prints a sideways view of the tree in ASCII form, @@ -1498,6 +1536,7 @@ assert(std.algorithm.equal(rbt[], [5])); */ void printTree(Node n, int indent = 0) { + import std.stdio; if(n !is null) { printTree(n.right, indent + 2); @@ -1527,6 +1566,7 @@ assert(std.algorithm.equal(rbt[], [5])); // int recurse(Node n, string path) { + import std.stdio; if(n is null) return 1; if(n.parent.left !is n && n.parent.right !is n) @@ -1554,7 +1594,7 @@ assert(std.algorithm.equal(rbt[], [5])); if(l != r) { writeln("bad tree at:"); - printTree(n); + debug printTree(n); throw new Exception("Node at path " ~ path ~ " has different number of black nodes on left and right paths"); } return l + (n.color == n.color.Black ? 1 : 0); @@ -1566,7 +1606,7 @@ assert(std.algorithm.equal(rbt[], [5])); } catch(Exception e) { - printTree(_end.left, 0); + debug printTree(_end.left, 0); throw e; } } @@ -1606,18 +1646,20 @@ assert(std.algorithm.equal(rbt[], [5])); } //Verify Example for removeKey. -unittest +pure unittest { + import std.algorithm : equal; auto rbt = redBlackTree!true(0, 1, 1, 1, 4, 5, 7); rbt.removeKey(1, 4, 7); - assert(std.algorithm.equal(rbt[], [0, 1, 1, 5])); + assert(equal(rbt[], [0, 1, 1, 5])); rbt.removeKey(1, 1, 0); - assert(std.algorithm.equal(rbt[], [5])); + assert(equal(rbt[], [5])); } //Tests for removeKey -unittest +pure unittest { + import std.algorithm : equal; { auto rbt = redBlackTree(["hello", "world", "foo", "bar"]); assert(equal(rbt[], ["bar", "foo", "hello", "world"])); @@ -1645,7 +1687,7 @@ unittest } } -unittest +pure unittest { void test(T)() { @@ -1697,7 +1739,7 @@ auto redBlackTree(alias less, bool allowDuplicates, E)(E[] elems...) } /// -unittest +pure unittest { auto rbt1 = redBlackTree(0, 1, 5, 7); auto rbt2 = redBlackTree!string("hello", "world"); @@ -1707,7 +1749,7 @@ unittest } //Combinations not in examples. -unittest +pure unittest { auto rbt1 = redBlackTree!(true, string)("hello", "hello"); auto rbt2 = redBlackTree!((a, b){return a < b;}, double)(5.1, 2.3); @@ -1715,14 +1757,17 @@ unittest } //Range construction. -unittest +pure unittest { + import std.algorithm : equal; + import std.range : iota; auto rbt = new RedBlackTree!(int, "a > b")(iota(5)); assert(equal(rbt[], [4, 3, 2, 1, 0])); } -unittest +pure unittest { + import std.array : array; auto rt1 = redBlackTree(5, 4, 3, 2, 1); assert(rt1.length == 5); assert(array(rt1[]) == [1, 2, 3, 4, 5]); diff --git a/std/container/slist.d b/std/container/slist.d index 750922cefdf..819ed82e3fc 100644 --- a/std/container/slist.d +++ b/std/container/slist.d @@ -1,6 +1,24 @@ +/** +This module implements a singly-linked list container. + +This module is a submodule of $(LINK2 std_container_package.html, std.container). + +Source: $(PHOBOSSRC std/container/_slist.d) +Macros: +WIKI = Phobos/StdContainer +TEXTWITHCOMMAS = $0 + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(WEB +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) +*/ module std.container.slist; -import std.exception, std.range, std.traits; public import std.container.util; /** @@ -10,6 +28,11 @@ public import std.container.util; */ struct SList(T) { + import std.exception : enforce; + import std.range : Take; + import std.range.primitives; + import std.traits; + private struct Node { Node * _next; @@ -543,12 +566,14 @@ unittest unittest { + import std.range.primitives; auto s = SList!int(1, 2, 5, 10); assert(walkLength(s[]) == 4); } unittest { + import std.range : take; auto src = take([0, 1, 2, 3], 3); auto s = SList!int(src); assert(s == SList!int(0, 1, 2)); @@ -643,6 +668,7 @@ unittest unittest { + import std.range : take; auto s = SList!int(1, 2, 3, 4); auto r = take(s[], 2); assert(s.insertAfter(r, 5) == 1); @@ -663,6 +689,7 @@ unittest unittest { + import std.range.primitives; auto s = SList!int(1, 2, 3, 4, 5); auto r = s[]; popFrontN(r, 3); @@ -683,6 +710,7 @@ unittest unittest { import std.algorithm : equal; + import std.range; auto s = SList!int(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); auto r = s[]; @@ -696,6 +724,7 @@ unittest unittest { + import std.range.primitives; auto lst = SList!int(1, 5, 42, 9); assert(!lst.empty); assert(lst.front == 1); diff --git a/std/container/util.d b/std/container/util.d index 9ff49a7b7a4..3d013a8dbd3 100644 --- a/std/container/util.d +++ b/std/container/util.d @@ -1,6 +1,23 @@ -module std.container.util; +/** +This module contains some common utilities used by containers. + +This module is a submodule of $(LINK2 std_container_package.html, std.container). + +Source: $(PHOBOSSRC std/container/_util.d) +Macros: +WIKI = Phobos/StdContainer +TEXTWITHCOMMAS = $0 -import std.algorithm; +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(WEB +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(WEB erdani.com, Andrei Alexandrescu) +*/ +module std.container.util; /** Returns an initialized object. This function is mainly for eliminating @@ -13,7 +30,19 @@ if (is(T == struct) || is(T == class)) T make(Args...)(Args arguments) if (is(T == struct) && __traits(compiles, T(arguments))) { - return T(arguments); + // constructing an std.container.Array without arguments, + // does not initialize its payload and is equivalent + // to a null reference. We therefore construct an empty container + // by passing an empty array to its constructor. + // Issue #13872. + static if(arguments.length == 0) + { + import std.range; + alias ET = ElementType!(T.Range); + return T(ET[].init); + } + else + return T(arguments); } T make(Args...)(Args arguments) @@ -23,10 +52,13 @@ if (is(T == struct) || is(T == class)) } } + /// unittest { import std.container; + import std.algorithm : equal; + import std.algorithm : equal; auto arr = make!(Array!int)([4, 2, 3, 1]); assert(equal(arr[], [4, 2, 3, 1])); @@ -42,6 +74,7 @@ unittest unittest { import std.container; + import std.algorithm : equal; auto arr1 = make!(Array!dchar)(); assert(arr1.empty); @@ -58,6 +91,7 @@ unittest unittest { import std.container; + import std.algorithm : equal; auto a = make!(DList!int)(1,2,3,4); auto b = make!(DList!int)(1,2,3,4); @@ -95,6 +129,7 @@ unittest { import std.container.array, std.container.rbtree, std.container.slist; import std.range : iota; + import std.algorithm : equal; auto arr = make!Array(iota(5)); assert(equal(arr[], [0, 1, 2, 3, 4])); @@ -113,6 +148,34 @@ unittest unittest { import std.container.rbtree; + import std.algorithm : equal; + auto rbtmin = make!(RedBlackTree, "a < b", false)(3, 2, 2, 1); assert(equal(rbtmin[], [1, 2, 3])); } + +// Issue 13872 +unittest +{ + import std.container; + + auto tree1 = make!(RedBlackTree!int)(); + auto refToTree1 = tree1; + refToTree1.insert(1); + assert(1 in tree1); + + auto array1 = make!(Array!int)(); + auto refToArray1 = array1; + refToArray1.insertBack(1); + assert(!array1.empty); + + auto slist = make!(SList!int)(); + auto refToSlist = slist; + refToSlist.insert(1); + assert(!slist.empty); + + auto dlist = make!(DList!int)(); + auto refToDList = dlist; + refToDList.insert(1); + assert(!dlist.empty); +} diff --git a/std/conv.d b/std/conv.d index aa16f7e9c30..e4375752ab6 100644 --- a/std/conv.d +++ b/std/conv.d @@ -21,10 +21,17 @@ WIKI = Phobos/StdConv */ module std.conv; -import core.checkedint, core.stdc.string; -import std.algorithm, std.array, std.ascii, std.exception, std.range, - std.string, std.traits, std.typecons, std.typetuple, std.utf; -import std.format; +public import std.ascii : LetterCase; + +import std.range.primitives; +import std.traits; +import std.typetuple; + +private string convFormat(Char, Args...)(in Char[] fmt, Args args) +{ + import std.format : format; + return std.format.format(fmt, args); +} /* ************* Exceptions *************** */ @@ -99,6 +106,7 @@ private if (isSomeString!T) { import std.format : FormatSpec, formatValue; + import std.array : appender; auto w = appender!T(); FormatSpec!(ElementEncodingType!T) f; @@ -315,6 +323,7 @@ template to(T) // Tests for issue 8729: do NOT skip leading WS @safe pure unittest { + import std.exception; foreach (T; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong)) { assertThrown!ConvException(to!T(" 0")); @@ -354,11 +363,13 @@ T toImpl(T, S)(S value) // Conversion from integer to integer, and changing its sign static if (isUnsignedInt!S && isSignedInt!T && S.sizeof == T.sizeof) { // unsigned to signed & same size + import std.exception : enforce; enforce(value <= cast(S)T.max, new ConvOverflowException("Conversion positive overflow")); } else static if (isSignedInt!S && isUnsignedInt!T) { // signed to unsigned + import std.exception : enforce; enforce(0 <= value, new ConvOverflowException("Conversion negative overflow")); } @@ -383,9 +394,10 @@ T toImpl(T, S)(S value) // Tests for issue 6377 @safe pure unittest { + import std.exception; // Conversion between same size foreach (S; TypeTuple!(byte, short, int, long)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 alias U = Unsigned!S; foreach (Sint; TypeTuple!(S, const S, immutable S)) @@ -401,12 +413,12 @@ T toImpl(T, S)(S value) assertThrown!ConvOverflowException(to!Uint(sn), text(Sint.stringof, ' ', Uint.stringof, ' ', un)); } - } + }(); // Conversion between different size foreach (i, S1; TypeTuple!(byte, short, int, long)) foreach ( S2; TypeTuple!(byte, short, int, long)[i+1..$]) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 alias U1 = Unsigned!S1; alias U2 = Unsigned!S2; @@ -446,7 +458,7 @@ T toImpl(T, S)(S value) Sint sn = -1; assertThrown!ConvOverflowException(to!Uint(sn)); } - } + }(); } /* @@ -466,7 +478,7 @@ T toImpl(T, S)(ref S s) } /** -When source type supports member template function opCast, is is used. +When source type supports member template function opCast, it is used. */ T toImpl(T, S)(S value) if (!isImplicitlyConvertible!(S, T) && @@ -694,6 +706,7 @@ T toImpl(T, S)(S value) @safe pure unittest { + import std.exception; // Testing object conversions class A {} class B : A {} @@ -707,6 +720,8 @@ T toImpl(T, S)(S value) // Unittest for 6288 @safe pure unittest { + import std.exception; + alias Identity(T) = T; alias toConst(T) = const T; alias toShared(T) = shared T; @@ -731,7 +746,7 @@ T toImpl(T, S)(S value) foreach (m1; TypeTuple!(0,1,2,3,4)) // enumerate modifiers foreach (m2; TypeTuple!(0,1,2,3,4)) // ditto - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 alias srcmod = AddModifier!m1; alias tgtmod = AddModifier!m2; //pragma(msg, srcmod!Object, " -> ", tgtmod!Object, ", convertible = ", @@ -767,7 +782,7 @@ T toImpl(T, S)(S value) static assert(!is(typeof(to!(tgtmod!C)(srcmod!I.init)))); // I to C static assert(!is(typeof(to!(tgtmod!J)(srcmod!I.init)))); // I to J } - } + }(); } /** @@ -804,7 +819,7 @@ $(UL T toImpl(T, S)(S value) if (!(isImplicitlyConvertible!(S, T) && !isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) && - isExactSomeString!T) + !isInfinite!S && isExactSomeString!T) { static if (isExactSomeString!S && value[0].sizeof == ElementEncodingType!T.sizeof) { @@ -822,6 +837,7 @@ T toImpl(T, S)(S value) } else static if (isExactSomeString!S) { + import std.array : appender; // other string-to-string //Use Appender directly instead of toStr, which also uses a formatedWrite auto w = appender!T(); @@ -835,6 +851,8 @@ T toImpl(T, S)(S value) } else static if (is(S == void[]) || is(S == const(void)[]) || is(S == immutable(void)[])) { + import core.stdc.string : memcpy; + import std.exception : enforce; // Converting void array to string alias Char = Unqual!(ElementEncodingType!T); auto raw = cast(const(ubyte)[]) value; @@ -848,8 +866,9 @@ T toImpl(T, S)(S value) } else static if (isPointer!S && is(S : const(char)*)) { + import core.stdc.string : strlen; // It is unsafe because we cannot guarantee that the pointer is null terminated. - return value ? cast(T) value[0 .. strlen(value)].dup : cast(string)null; + return value ? cast(T) value[0 .. strlen(value)].dup : null; } else static if (isSomeString!T && is(S == enum)) { @@ -875,6 +894,7 @@ T toImpl(T, S)(S value) } import std.format : FormatSpec, formatValue; + import std.array : appender; //Default case, delegate to format //Note: we don't call toStr directly, to avoid duplicate work. @@ -893,6 +913,13 @@ T toImpl(T, S)(S value) } } +// Bugzilla 14042 +unittest +{ + immutable(char)* ptr = "hello".ptr; + auto result = ptr.to!(char[]); +} + /* Check whether type $(D T) can be used in a switch statement. This is useful for compile-time generation of switch case statements. @@ -923,6 +950,7 @@ if (is (T == immutable) && isExactSomeString!T && is(S == enum)) @safe pure unittest { + import std.exception; void dg() { // string to string conversion @@ -1021,6 +1049,7 @@ if (is (T == immutable) && isExactSomeString!T && is(S == enum)) @safe pure nothrow unittest { + import std.exception; // Conversion representing integer values with string foreach (Int; TypeTuple!(ubyte, ushort, uint, ulong)) @@ -1070,7 +1099,8 @@ if (is (T == immutable) && isExactSomeString!T && is(S == enum)) { // Conversion representing associative array with string int[string] a = ["0":1, "1":2]; - assert(to!string(a) == `["0":1, "1":2]`); + assert(to!string(a) == `["0":1, "1":2]` || + to!string(a) == `["1":2, "0":1]`); } unittest @@ -1309,6 +1339,8 @@ T toImpl(T, S)(S value) @safe pure unittest { + import std.exception; + dchar a = ' '; assert(to!char(a) == ' '); a = 300; @@ -1332,6 +1364,8 @@ T toImpl(T, S)(S value) unittest { + import std.exception; + // Narrowing conversions from enum -> integral should be allowed, but they // should throw at runtime if the enum value doesn't fit in the target // type. @@ -1374,13 +1408,15 @@ T toImpl(T, S)(S value) static if (isStaticArray!T) { + import std.exception : enforce; auto res = to!(E[])(value); enforce!ConvException(T.length == res.length, - format("Length mismatch when converting to static array: %s vs %s", T.length, res.length)); + convFormat("Length mismatch when converting to static array: %s vs %s", T.length, res.length)); return res[0 .. T.length]; } else { + import std.array : appender; auto w = appender!(E[])(); w.reserve(value.length); foreach (i, ref e; value) @@ -1393,6 +1429,8 @@ T toImpl(T, S)(S value) @safe pure unittest { + import std.exception; + // array to array conversions uint[] a = ([ 1u, 2, 3 ]).dup; auto b = to!(float[])(a); @@ -1477,6 +1515,7 @@ T toImpl(T, S)(S value) } @safe /*pure */unittest // Bugzilla 8705, from doc { + import std.exception; int[string][double[int[]]] a; auto b = to!(short[wstring][string[double[]]])(a); a = [null:["hello":int.max]]; @@ -1743,12 +1782,12 @@ T toImpl(T, S)(S value) if (Member == value) return Member; } - - throw new ConvException(format("Value (%s) does not match any member value of enum '%s'", value, T.stringof)); + throw new ConvException(convFormat("Value (%s) does not match any member value of enum '%s'", value, T.stringof)); } @safe pure unittest { + import std.exception; enum En8143 : int { A = 10, B = 20, C = 30, D = 20 } enum En8143[][] m3 = to!(En8143[][])([[10, 30], [30, 10]]); static assert(m3 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]); @@ -1763,17 +1802,6 @@ T toImpl(T, S)(S value) /*************************************************************** Rounded conversion from floating point to integral. -Example: ---------------- -assert(roundTo!int(3.14) == 3); -assert(roundTo!int(3.49) == 3); -assert(roundTo!int(3.5) == 4); -assert(roundTo!int(3.999) == 4); -assert(roundTo!int(-3.14) == -3); -assert(roundTo!int(-3.49) == -3); -assert(roundTo!int(-3.5) == -4); -assert(roundTo!int(-3.999) == -4); ---------------- Rounded conversions do not work with non-integral target types. */ @@ -1789,6 +1817,7 @@ template roundTo(Target) } } +/// unittest { assert(roundTo!int(3.14) == 3); @@ -1800,7 +1829,11 @@ unittest assert(roundTo!int(-3.5) == -4); assert(roundTo!int(-3.999) == -4); assert(roundTo!(const int)(to!(const double)(-3.999)) == -4); +} +unittest +{ + import std.exception; // boundary values foreach (Int; TypeTuple!(byte, ubyte, short, ushort, int, uint)) { @@ -1819,37 +1852,23 @@ unittest * could not convert the entire input. It still throws if an overflow * occurred during conversion or if no character of the input * was meaningfully converted. - * - * Example: - * -------------- - * string test = "123 \t 76.14"; - * auto a = parse!uint(test); - * assert(a == 123); - * assert(test == " \t 76.14"); // parse bumps string - * munch(test, " \t\n\r"); // skip ws - * assert(test == "76.14"); - * auto b = parse!double(test); - * assert(b == 76.14); - * assert(test == ""); - * -------------- */ - Target parse(Target, Source)(ref Source s) if (isInputRange!Source && - !isExactSomeString!Source && isSomeChar!(ElementType!Source) && is(Unqual!Target == bool)) { + import std.ascii : toLower; if (!s.empty) { - auto c1 = std.ascii.toLower(s.front); + auto c1 = toLower(s.front); bool result = (c1 == 't'); if (result || c1 == 'f') { s.popFront(); foreach (c; result ? "rue" : "alse") { - if (s.empty || std.ascii.toLower(s.front) != c) + if (s.empty || toLower(s.front) != c) goto Lerr; s.popFront(); } @@ -1860,8 +1879,25 @@ Lerr: throw parseError("bool should be case-insensitive 'true' or 'false'"); } +/// +unittest +{ + import std.string : munch; + string test = "123 \t 76.14"; + auto a = parse!uint(test); + assert(a == 123); + assert(test == " \t 76.14"); // parse bumps string + munch(test, " \t\n\r"); // skip ws + assert(test == "76.14"); + auto b = parse!double(test); + assert(b == 76.14); + assert(test == ""); +} + unittest { + import std.exception; + import std.algorithm : equal; struct InputString { string _s; @@ -1949,9 +1985,11 @@ Target parse(Target, Source)(ref Source s) if (c > 9) break; - if (v < Target.max/10 || - (v == Target.max/10 && c <= maxLastDigit + sign)) + if (v >= 0 && (v < Target.max/10 || + (v == Target.max/10 && c <= maxLastDigit + sign))) { + // Note: `v` can become negative here in case of parsing + // the most negative value: v = cast(Target) (v * 10 + c); s.popFront(); } @@ -2070,6 +2108,7 @@ Lerr: @safe pure unittest { + import std.exception; // parsing error check foreach (Int; TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong)) { @@ -2144,11 +2183,21 @@ Lerr: @safe pure unittest { + import std.exception; assertCTFEable!({ string s = "1234abc"; assert(parse! int(s) == 1234 && s == "abc"); }); assertCTFEable!({ string s = "-1234abc"; assert(parse! int(s) == -1234 && s == "abc"); }); assertCTFEable!({ string s = "1234abc"; assert(parse!uint(s) == 1234 && s == "abc"); }); } +// Issue 13931 +@safe pure unittest +{ + import std.exception; + + assertThrown!ConvOverflowException("-21474836480".to!int()); + assertThrown!ConvOverflowException("-92233720368547758080".to!long()); +} + /// ditto Target parse(Target, Source)(ref Source s, uint radix) if (isSomeChar!(ElementType!Source) && @@ -2159,6 +2208,7 @@ in } body { + import core.checkedint : mulu, addu; if (radix == 10) return parse!Target(s); @@ -2242,6 +2292,7 @@ Lerr: @safe pure unittest // bugzilla 7302 { + import std.range : cycle; auto r = cycle("2A!"); auto u = parse!uint(r, 16); assert(u == 42); @@ -2250,6 +2301,7 @@ Lerr: @safe pure unittest // bugzilla 13163 { + import std.exception; foreach (s; ["fff", "123"]) assertThrown!ConvOverflowException(s.parse!ubyte(16)); } @@ -2258,6 +2310,7 @@ Target parse(Target, Source)(ref Source s) if (isExactSomeString!Source && is(Target == enum)) { + import std.algorithm : startsWith; Target result; size_t longest_match = 0; @@ -2284,6 +2337,8 @@ Target parse(Target, Source)(ref Source s) unittest { + import std.exception; + enum EB : bool { a = true, b = false, c = a } enum EU { a, b, c } enum EI { a = -1, b = 0, c = 1 } @@ -2315,7 +2370,9 @@ Target parse(Target, Source)(ref Source p) if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && isFloatingPoint!Target && !is(Target == enum)) { - static import core.stdc.math/* : HUGE_VAL*/; + import std.ascii : isDigit, isAlpha, toLower, toUpper, isHexDigit; + import std.exception : enforce; + import core.stdc.math : HUGE_VAL; static immutable real[14] negtab = [ 1e-4096L,1e-2048L,1e-1024L,1e-512L,1e-256L,1e-128L,1e-64L,1e-32L, @@ -2328,7 +2385,7 @@ Target parse(Target, Source)(ref Source p) ConvException bailOut()(string msg = null, string fn = __FILE__, size_t ln = __LINE__) { - if (!msg) + if (msg == null) msg = "Floating point conversion error"; return new ConvException(text(msg, " for input \"", p, "\"."), fn, ln); } @@ -2342,7 +2399,7 @@ Target parse(Target, Source)(ref Source p) sign++; p.popFront(); enforce(!p.empty, bailOut()); - if (std.ascii.toLower(p.front) == 'i') + if (toLower(p.front) == 'i') goto case 'i'; enforce(!p.empty, bailOut()); break; @@ -2353,11 +2410,11 @@ Target parse(Target, Source)(ref Source p) case 'i': case 'I': p.popFront(); enforce(!p.empty, bailOut()); - if (std.ascii.toLower(p.front) == 'n') + if (toLower(p.front) == 'n') { p.popFront(); enforce(!p.empty, bailOut()); - if (std.ascii.toLower(p.front) == 'f') + if (toLower(p.front) == 'f') { // 'inf' p.popFront(); @@ -2400,7 +2457,7 @@ Target parse(Target, Source)(ref Source p) while (isHexDigit(i)) { anydigits = 1; - i = std.ascii.isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0'; + i = isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0'; if (ndigits < 16) { msdec = msdec * 16 + i; @@ -2586,14 +2643,14 @@ Target parse(Target, Source)(ref Source p) } else // not hex { - if (std.ascii.toUpper(p.front) == 'N' && !startsWithZero) + if (toUpper(p.front) == 'N' && !startsWithZero) { // nan p.popFront(); - enforce(!p.empty && std.ascii.toUpper(p.front) == 'A', + enforce(!p.empty && toUpper(p.front) == 'A', new ConvException("error converting input to floating point")); p.popFront(); - enforce(!p.empty && std.ascii.toUpper(p.front) == 'N', + enforce(!p.empty && toUpper(p.front) == 'N', new ConvException("error converting input to floating point")); // skip past the last 'n' p.popFront(); @@ -2706,7 +2763,7 @@ Target parse(Target, Source)(ref Source p) } } L6: // if overflow occurred - enforce(ldval != core.stdc.math.HUGE_VAL, new ConvException("Range error")); + enforce(ldval != HUGE_VAL, new ConvException("Range error")); L1: return (sign) ? -ldval : ldval; @@ -2714,7 +2771,8 @@ Target parse(Target, Source)(ref Source p) unittest { - import std.math : isnan, fabs; + import std.exception; + import std.math : isNaN, fabs; // Compare reals with given precision bool feq(in real rx, in real ry, in real precision = 0.000001L) @@ -2722,10 +2780,10 @@ unittest if (rx == ry) return 1; - if (isnan(rx)) - return cast(bool)isnan(ry); + if (isNaN(rx)) + return cast(bool)isNaN(ry); - if (isnan(ry)) + if (isNaN(ry)) return 0; return cast(bool)(fabs(rx - ry) <= precision); @@ -2753,7 +2811,7 @@ unittest assert(to!Float("0") is 0.0); assert(to!Float("-0") is -0.0); - assert(isnan(to!Float("nan"))); + assert(isNaN(to!Float("nan"))); assertThrown!ConvException(to!Float("\x00")); } @@ -2923,6 +2981,8 @@ unittest @safe pure unittest { + import std.exception; + // Bugzilla 4959 { auto s = "0 "; @@ -3009,29 +3069,14 @@ Target parse(Target, Source)(ref Source s) return result; } -// string to bool conversions -Target parse(Target, Source)(ref Source s) - if (isExactSomeString!Source && - is(Unqual!Target == bool)) -{ - if (s.length >= 4 && icmp(s[0 .. 4], "true") == 0) - { - s = s[4 .. $]; - return true; - } - if (s.length >= 5 && icmp(s[0 .. 5], "false") == 0) - { - s = s[5 .. $]; - return false; - } - throw parseError("bool should be case-insensitive 'true' or 'false'"); -} /* Tests for to!bool and parse!bool */ @safe pure unittest { + import std.exception; + assert (to!bool("TruE") == true); assert (to!bool("faLse"d) == false); assertThrown!ConvException(to!bool("maybe")); @@ -3053,21 +3098,26 @@ Target parse(Target, Source)(ref Source s) assert(b == true); } -// string to null literal conversions +// input range to null literal conversions Target parse(Target, Source)(ref Source s) - if (isExactSomeString!Source && + if (isInputRange!Source && + isSomeChar!(ElementType!Source) && is(Unqual!Target == typeof(null))) { - if (s.length >= 4 && icmp(s[0 .. 4], "null") == 0) + import std.ascii : toLower; + foreach (c; "null") { - s = s[4 .. $]; - return null; + if (s.empty || toLower(s.front) != c) + throw parseError("null should be case-insensitive 'null'"); + s.popFront(); } - throw parseError("null should be case-insensitive 'null'"); + return null; } @safe pure unittest { + import std.exception; + alias NullType = typeof(null); auto s1 = "null"; assert(parse!NullType(s1) is null); @@ -3088,12 +3138,13 @@ Target parse(Target, Source)(ref Source s) //Used internally by parse Array/AA, to remove ascii whites package void skipWS(R)(ref R r) { + import std.ascii : isWhite; static if (isSomeString!R) { //Implementation inspired from stripLeft. foreach (i, dchar c; r) { - if (!std.ascii.isWhite(c)) + if (!isWhite(c)) { r = r[i .. $]; return; @@ -3104,7 +3155,7 @@ package void skipWS(R)(ref R r) } else { - for (; !r.empty && std.ascii.isWhite(r.front); r.popFront()) + for (; !r.empty && isWhite(r.front); r.popFront()) {} } } @@ -3181,6 +3232,8 @@ unittest @safe pure unittest { + import std.exception; + //Check proper failure auto s = "[ 1 , 2 , 3 ]"; foreach (i ; 0..s.length-1) @@ -3271,6 +3324,8 @@ Lfewerr: @safe pure unittest { + import std.exception; + auto s1 = "[1,2,3,4]"; auto sa1 = parse!(int[4])(s1); assert(sa1 == [1,2,3,4]); @@ -3345,6 +3400,8 @@ Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket @safe pure unittest { + import std.exception; + //Check proper failure auto s = "[1:10, 2:20, 3:30]"; foreach (i ; 0 .. s.length-1) @@ -3364,6 +3421,7 @@ private dchar parseEscape(Source)(ref Source s) dchar getHexDigit()(ref Source s_ = s) // workaround { + import std.ascii : isAlpha, isHexDigit; if (s_.empty) throw parseError("Unterminated escape sequence"); s_.popFront(); @@ -3372,7 +3430,7 @@ private dchar parseEscape(Source)(ref Source s) dchar c = s_.front; if (!isHexDigit(c)) throw parseError("Hex digit is missing"); - return std.ascii.isAlpha(c) ? ((c & ~0x20) - ('A' - 10)) : c - '0'; + return isAlpha(c) ? ((c & ~0x20) - ('A' - 10)) : c - '0'; } dchar result; @@ -3449,6 +3507,8 @@ private dchar parseEscape(Source)(ref Source s) @safe pure unittest { + import std.exception; + string[] ss = [ `hello!`, //Not an escape `\`, //Premature termination @@ -3471,6 +3531,7 @@ Target parseElement(Target, Source)(ref Source s) if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && isExactSomeString!Target) { + import std.array : appender; auto result = appender!Target(); // parse array of chars @@ -3592,16 +3653,6 @@ user specifically asks for a $(D long) with the $(D L) suffix, always give the $(D long). Give an unsigned iff it is asked for with the $(D U) or $(D u) suffix. _Octals created from integers preserve the type of the passed-in integral. - -Example: ----- -// same as 0177 -auto x = octal!177; -// octal is a compile-time device -enum y = octal!160; -// Create an unsigned octal -auto z = octal!"1_000_000u"; ----- */ @property int octal(string num)() if((octalFitsInInt!(num) && !literalIsLong!(num)) && !literalIsUnsigned!(num)) @@ -3637,15 +3688,20 @@ template octal(alias s) enum auto octal = octal!(typeof(s), to!string(s)); } +/// +unittest +{ + // same as 0177 + auto x = octal!177; + // octal is a compile-time device + enum y = octal!160; + // Create an unsigned octal + auto z = octal!"1_000_000u"; +} + /* Takes a string, num, which is an octal literal, and returns its value, in the type T specified. - - So: - - int a = octal!(int, "10"); - - assert(a == 8); */ @property T octal(T, string num)() if (isOctalLiteral!num) @@ -3668,6 +3724,14 @@ template octal(alias s) return value; } +/// +unittest +{ + int a = octal!(int, "10"); + + assert(a == 8); +} + /* Take a look at int.max and int.max+1 in octal and the logic for this function follows directly. @@ -3851,7 +3915,7 @@ private template emplaceImpl(T) ref UT emplaceImpl()(ref UT chunk) { static assert (is(typeof({static T i;})), - format("Cannot emplace a %1$s because %1$s.this() is annotated with @disable.", T.stringof)); + convFormat("Cannot emplace a %1$s because %1$s.this() is annotated with @disable.", T.stringof)); return emplaceInitializer(chunk); } @@ -3860,7 +3924,7 @@ private template emplaceImpl(T) ref UT emplaceImpl(Arg)(ref UT chunk, auto ref Arg arg) { static assert(is(typeof({T t = arg;})), - format("%s cannot be emplaced from a %s.", T.stringof, Arg.stringof)); + convFormat("%s cannot be emplaced from a %s.", T.stringof, Arg.stringof)); static if (isStaticArray!T) { @@ -3876,6 +3940,7 @@ private template emplaceImpl(T) chunk = arg; else static if (is(UArg == UT)) { + import core.stdc.string : memcpy; memcpy(&chunk, &arg, T.sizeof); static if (hasElaborateCopyConstructor!T) typeid(T).postblit(cast(void*)&chunk); @@ -3890,6 +3955,7 @@ private template emplaceImpl(T) chunk[] = arg[]; else static if (is(Unqual!(ElementEncodingType!Arg) == UE)) { + import core.stdc.string : memcpy; assert(N == chunk.length, "Array length missmatch in emplace"); memcpy(cast(void*)&chunk, arg.ptr, T.sizeof); static if (hasElaborateCopyConstructor!T) @@ -3905,6 +3971,7 @@ private template emplaceImpl(T) chunk[] = arg; else static if (is(UArg == Unqual!E)) { + import core.stdc.string : memcpy; //Note: We copy everything, and then postblit just once. //This is as exception safe as what druntime can provide us. foreach(i; 0 .. N) @@ -3928,7 +3995,7 @@ private template emplaceImpl(T) .emplaceImpl!E(chunk[i], arg); } else - static assert(0, format("Sorry, this implementation doesn't know how to emplace a %s with a %s", T.stringof, Arg.stringof)); + static assert(0, convFormat("Sorry, this implementation doesn't know how to emplace a %s with a %s", T.stringof, Arg.stringof)); return chunk; } @@ -3953,6 +4020,7 @@ private template emplaceImpl(T) chunk = args[0]; else { + import core.stdc.string : memcpy; memcpy(&chunk, &args[0], T.sizeof); static if (hasElaborateCopyConstructor!T) typeid(T).postblit(&chunk); @@ -3993,11 +4061,11 @@ private template emplaceImpl(T) { //We can't emplace. Try to diagnose a disabled postblit. static assert(!(Args.length == 1 && is(Args[0] : T)), - format("Cannot emplace a %1$s because %1$s.this(this) is annotated with @disable.", T.stringof)); + convFormat("Cannot emplace a %1$s because %1$s.this(this) is annotated with @disable.", T.stringof)); //We can't emplace. static assert(false, - format("%s cannot be emplaced from %s.", T.stringof, Args[].stringof)); + convFormat("%s cannot be emplaced from %s.", T.stringof, Args[].stringof)); } return chunk; @@ -4010,6 +4078,7 @@ private ref T emplaceInitializer(T)(ref T chunk) @trusted pure nothrow chunk = T.init; else { + import core.stdc.string : memcpy; static immutable T init = T.init; memcpy(&chunk, &init, T.sizeof); } @@ -4019,7 +4088,7 @@ private deprecated("Using static opCall for emplace is deprecated. Plase use emp ref T emplaceOpCaller(T, Args...)(ref T chunk, auto ref Args args) { static assert (is(typeof({T t = T.opCall(args);})), - format("%s.opCall does not return adequate data for construction.", T.stringof)); + convFormat("%s.opCall does not return adequate data for construction.", T.stringof)); return emplaceImpl!T(chunk, chunk.opCall(args)); } @@ -4548,7 +4617,7 @@ version(unittest) { int j; __std_conv_S s; - ref __std_conv_S foo() @property {s.i = j; return s;} + ref __std_conv_S foo() return @property {s.i = j; return s;} alias foo this; } static assert(is(__std_conv_SS : __std_conv_S)); @@ -4702,6 +4771,8 @@ unittest unittest //@@@9559@@@ { + import std.algorithm : map; + import std.typecons : Nullable; alias I = Nullable!int; auto ints = [0, 1, 2].map!(i => i & 1 ? I.init : I(i))(); auto asArray = std.array.array(ints); @@ -4723,9 +4794,9 @@ unittest //http://forum.dlang.org/thread/nxbdgtdlmwscocbiypjs@forum.dlang.org invariant() { if(j == 0) - assert(a.i.isNaN, "why is 'j' zero?? and i is not NaN?"); + assert(a.i.isNaN(), "why is 'j' zero?? and i is not NaN?"); else - assert(!a.i.isNaN); + assert(!a.i.isNaN()); } SysTime when; // comment this line avoid the breakage int j; @@ -4886,11 +4957,12 @@ unittest private void testEmplaceChunk(void[] chunk, size_t typeSize, size_t typeAlignment, string typeName) { + import std.exception : enforce; enforce!ConvException(chunk.length >= typeSize, - format("emplace: Chunk size too small: %s < %s size = %s", + convFormat("emplace: Chunk size too small: %s < %s size = %s", chunk.length, typeName, typeSize)); enforce!ConvException((cast(size_t) chunk.ptr) % typeAlignment == 0, - format("emplace: Misaligned memory block (0x%X): it must be %s-byte aligned for type %s", + convFormat("emplace: Misaligned memory block (0x%X): it must be %s-byte aligned for type %s", chunk.ptr, typeAlignment, typeName)); } @@ -4960,6 +5032,7 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) return emplace(cast(T*) chunk.ptr, args); } +/// unittest { struct S @@ -5012,6 +5085,7 @@ unittest unittest { + import std.algorithm : equal, map; // Check fix for http://d.puremagic.com/issues/show_bug.cgi?id=2971 assert(equal(map!(to!int)(["42", "34", "345"]), [42, 34, 345])); } @@ -5041,6 +5115,7 @@ void toTextRange(T, W)(T value, W writer) unittest { + import std.array : appender; auto result = appender!(char[])(); toTextRange(-1, result); assert(result.data == "-1"); @@ -5048,7 +5123,7 @@ unittest /** - Returns the corresponding unsigned value for $(D x) (e.g. if $(D x) has type + Returns the corresponding _unsigned value for $(D x) (e.g. if $(D x) has type $(D int), it returns $(D cast(uint) x)). The advantage compared to the cast is that you do not need to rewrite the cast if $(D x) later changes type (e.g from $(D int) to $(D long)). @@ -5064,10 +5139,12 @@ auto unsigned(T)(T x) if (isIntegral!T) /// unittest { - uint s = 42; + immutable int s = 42; auto u1 = unsigned(s); //not qualified + static assert(is(typeof(u1) == uint)); Unsigned!(typeof(s)) u2 = unsigned(s); //same qualification - immutable u3 = unsigned(s); //totally qualified + static assert(is(typeof(u2) == immutable uint)); + immutable u3 = unsigned(s); //explicitly qualified } unittest @@ -5120,7 +5197,7 @@ unittest /** - Returns the corresponding signed value for $(D x) (e.g. if $(D x) has type + Returns the corresponding _signed value for $(D x) (e.g. if $(D x) has type $(D uint), it returns $(D cast(int) x)). The advantage compared to the cast is that you do not need to rewrite the cast if $(D x) later changes type (e.g from $(D uint) to $(D ulong)). @@ -5136,10 +5213,12 @@ auto signed(T)(T x) if (isIntegral!T) /// unittest { - uint u = 42; - auto s1 = unsigned(u); //not qualified - Unsigned!(typeof(u)) s2 = unsigned(u); //same qualification - immutable s3 = unsigned(u); //totally qualified + immutable uint u = 42; + auto s1 = signed(u); //not qualified + static assert(is(typeof(s1) == int)); + Signed!(typeof(u)) s2 = signed(u); //same qualification + static assert(is(typeof(s2) == immutable int)); + immutable s3 = signed(u); //explicitly qualified } unittest @@ -5180,3 +5259,74 @@ unittest ulong l = 0; auto t = l.to!Test; } + +/** + A wrapper on top of the built-in cast operator that allows one to restrict + casting of the original type of the value. + + A common issue with using a raw cast is that it may silently continue to + compile even if the value's type has changed during refactoring, + which breaks the initial assumption about the cast. + + Params: + From = The type to cast from. The programmer must ensure it is legal + to make this cast. + To = The type to cast to + value = The value to cast. It must be of type $(D From), + otherwise a compile-time error is emitted. + + Returns: + the value after the cast, returned by reference if possible + */ +template castFrom(From) +{ + auto ref to(To, T)(auto ref T value) @system + { + static assert ( + is(From == T), + "the value to cast is not of specified type '" ~ From.stringof ~ + "', it is of type '" ~ T.stringof ~ "'" + ); + + static assert ( + is(typeof(cast(To)value)), + "can't cast from '" ~ From.stringof ~ "' to '" ~ To.stringof ~ "'" + ); + + return cast(To) value; + } +} + +/// +unittest +{ + // Regular cast, which has been verified to be legal by the programmer: + { + long x; + auto y = cast(int) x; + } + + // However this will still compile if 'x' is changed to be a pointer: + { + long* x; + auto y = cast(int) x; + } + + // castFrom provides a more reliable alternative to casting: + { + long x; + auto y = castFrom!long.to!int(x); + } + + // Changing the type of 'x' will now issue a compiler error, + // allowing bad casts to be caught before it's too late: + { + long* x; + static assert ( + !__traits(compiles, castFrom!long.to!int(x)) + ); + + // if cast is still needed, must be changed to: + auto y = castFrom!(long*).to!int(x); + } +} diff --git a/std/cstream.d b/std/cstream.d index ad925f41f5d..ef54367f11f 100644 --- a/std/cstream.d +++ b/std/cstream.d @@ -5,14 +5,14 @@ * current standards. It will remain until we have a suitable replacement, * but be aware that it will not remain long term.) * - * The std.cstream module bridges std.c.stdio (or std.stdio) and std.stream. - * Both std.c.stdio and std.stream are publicly imported by std.cstream. + * 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: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Ben Hinkle * Source: $(PHOBOSSRC std/_cstream.d) */ @@ -23,8 +23,8 @@ */ module std.cstream; +public import core.stdc.stdio; public import std.stream; -public import std.c.stdio; version(unittest) import std.stdio; import std.algorithm; @@ -103,7 +103,7 @@ class CFile : Stream { * Ditto */ override char ungetc(char c) { - return cast(char)std.c.stdio.ungetc(c,cfile); + return cast(char)core.stdc.stdio.ungetc(c,cfile); } /** @@ -227,24 +227,24 @@ class CFile : Stream { } /** - * CFile wrapper of std.c.stdio.stdin (not seekable). + * CFile wrapper of core.stdc.stdio.stdin (not seekable). */ __gshared CFile din; /** - * CFile wrapper of std.c.stdio.stdout (not seekable). + * CFile wrapper of core.stdc.stdio.stdout (not seekable). */ __gshared CFile dout; /** - * CFile wrapper of std.c.stdio.stderr (not seekable). + * CFile wrapper of core.stdc.stdio.stderr (not seekable). */ __gshared CFile derr; shared static this() { // open standard I/O devices - din = new CFile(std.c.stdio.stdin,FileMode.In); - dout = new CFile(std.c.stdio.stdout,FileMode.Out); - derr = new CFile(std.c.stdio.stderr,FileMode.Out); + 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/std/csv.d b/std/csv.d index fb047f975cb..37856edfb9f 100644 --- a/std/csv.d +++ b/std/csv.d @@ -80,12 +80,8 @@ */ module std.csv; -import std.algorithm; -import std.array; import std.conv; -import std.exception; -import std.range; -import std.string; +import std.range.primitives; import std.traits; /** @@ -124,8 +120,8 @@ class CSVException : Exception this.col = col; } - override string toString() @safe pure - { + override string toString() @safe pure + { return "(Row: " ~ to!string(row) ~ ", Col: " ~ to!string(col) ~ ") " ~ msg; } @@ -133,20 +129,21 @@ class CSVException : Exception @safe pure unittest { - auto e1 = new Exception("Foobar"); - auto e2 = new CSVException("args", e1); - assert(e2.next is e1); + import std.string; + auto e1 = new Exception("Foobar"); + auto e2 = new CSVException("args", e1); + assert(e2.next is e1); - size_t r = 13; - size_t c = 37; + size_t r = 13; + size_t c = 37; - auto e3 = new CSVException("argv", r, c); - assert(e3.row == r); - assert(e3.col == c); + auto e3 = new CSVException("argv", r, c); + assert(e3.row == r); + assert(e3.col == c); - auto em = e3.toString(); - assert(em.indexOf("13") != -1); - assert(em.indexOf("37") != -1); + auto em = e3.toString(); + assert(em.indexOf("13") != -1); + assert(em.indexOf("37") != -1); } /** @@ -163,14 +160,14 @@ class IncompleteCellException : CSVException /// already been fed to the output range. dstring partialData; - this(string msg, string file = __FILE__, size_t line = __LINE__, - Throwable next = null) @safe pure + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) @safe pure { super(msg, file, line); } this(string msg, Throwable next, string file = __FILE__, size_t line = - __LINE__) @safe pure + __LINE__) @safe pure { super(msg, next, file, line); } @@ -178,9 +175,9 @@ class IncompleteCellException : CSVException @safe pure unittest { - auto e1 = new Exception("Foobar"); - auto e2 = new IncompleteCellException("args", e1); - assert(e2.next is e1); + auto e1 = new Exception("Foobar"); + auto e2 = new IncompleteCellException("args", e1); + assert(e2.next is e1); } /** @@ -205,14 +202,14 @@ class IncompleteCellException : CSVException */ class HeaderMismatchException : CSVException { - this(string msg, string file = __FILE__, size_t line = __LINE__, - Throwable next = null) @safe pure + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) @safe pure { super(msg, file, line); } - this(string msg, Throwable next, string file = __FILE__, - size_t line = __LINE__) @safe pure + this(string msg, Throwable next, string file = __FILE__, + size_t line = __LINE__) @safe pure { super(msg, next, file, line); } @@ -220,9 +217,9 @@ class HeaderMismatchException : CSVException @safe pure unittest { - auto e1 = new Exception("Foobar"); - auto e2 = new HeaderMismatchException("args", e1); - assert(e2.next is e1); + auto e1 = new Exception("Foobar"); + auto e2 = new HeaderMismatchException("args", e1); + assert(e2.next is e1); } /** @@ -319,12 +316,12 @@ enum Malformed */ auto csvReader(Contents = string,Malformed ErrorLevel = Malformed.throwException, Range, Separator = char)(Range input, Separator delimiter = ',', Separator quote = '"') - if(isInputRange!Range && is(ElementType!Range == dchar) + if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) && isSomeChar!(Separator) && !is(Contents T : T[U], U : string)) { return CsvReader!(Contents,ErrorLevel,Range, - ElementType!Range,string[]) + Unqual!(ElementType!Range),string[]) (input, delimiter, quote); } @@ -406,27 +403,28 @@ auto csvReader(Contents = string, Range, Header, Separator = char) (Range input, Header header, Separator delimiter = ',', Separator quote = '"') - if(isInputRange!Range && is(ElementType!Range == dchar) + if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) && isSomeChar!(Separator) && isForwardRange!Header && isSomeString!(ElementType!Header)) { return CsvReader!(Contents,ErrorLevel,Range, - ElementType!Range,Header) + Unqual!(ElementType!Range),Header) (input, header, delimiter, quote); } +/// auto csvReader(Contents = string, Malformed ErrorLevel = Malformed.throwException, Range, Header, Separator = char) (Range input, Header header, Separator delimiter = ',', Separator quote = '"') - if(isInputRange!Range && is(ElementType!Range == dchar) + if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) && isSomeChar!(Separator) && is(Header : typeof(null))) { return CsvReader!(Contents,ErrorLevel,Range, - ElementType!Range,string[]) + Unqual!(ElementType!Range),string[]) (input, cast(string[])null, delimiter, quote); } @@ -489,6 +487,8 @@ auto csvReader(Contents = string, // Test shorter row length exception @safe pure unittest { + import std.exception; + struct A { string a,b,c; @@ -542,6 +542,7 @@ auto csvReader(Contents = string, // Test input conversion interface @safe pure unittest { + import std.algorithm; string str = `76,26,22`; int[] ans = [76,26,22]; auto records = csvReader!int(str); @@ -588,6 +589,8 @@ unittest // Test header interface unittest { + import std.algorithm; + string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562"; auto records = csvReader!int(str, ["b"]); @@ -748,6 +751,16 @@ unittest (ir,cast(string[])null)) {} } +unittest // const/immutable dchars +{ + import std.algorithm: map; + import std.array: array; + const(dchar)[] c = "foo,bar\n"; + assert(csvReader(c).map!array.array == [["foo", "bar"]]); + immutable(dchar)[] i = "foo,bar\n"; + assert(csvReader(i).map!array.array == [["foo", "bar"]]); +} + /* * This struct is stored on the heap for when the structures * are passed around. @@ -756,7 +769,7 @@ private pure struct Input(Range, Malformed ErrorLevel) { Range range; size_t row, col; - static if(ErrorLevel == Malformed.throwException) + static if (ErrorLevel == Malformed.throwException) size_t rowLength; } @@ -766,23 +779,10 @@ private pure struct Input(Range, Malformed ErrorLevel) * This range is returned by the $(LREF csvReader) functions. It can be * created in a similar manner to allow $(D ErrorLevel) be set to $(LREF * Malformed).ignore if best guess processing should take place. - * - * Example for integer data: - * - * ------- - * int[] ans = [76,26,22]; - * auto records = CsvReader!(int,Malformed.ignore,string,char,string[]) - * (str, ';', '^'); - * - * foreach(record; records) { - * assert(equal(record, ans)); - * } - * ------- - * */ private struct CsvReader(Contents, Malformed ErrorLevel, Range, Separator, Header) - if(isSomeChar!Separator && isInputRange!Range - && is(ElementType!Range == dchar) + if (isSomeChar!Separator && isInputRange!Range + && is(Unqual!(ElementType!Range) == dchar) && isForwardRange!Header && isSomeString!(ElementType!Header)) { private: @@ -791,12 +791,12 @@ private: Separator _quote; size_t[] indices; bool _empty; - static if(is(Contents == struct) || is(Contents == class)) + static if (is(Contents == struct) || is(Contents == class)) { Contents recordContent; CsvRecord!(string, ErrorLevel, Range, Separator) recordRange; } - else static if(is(Contents T : T[U], U : string)) + else static if (is(Contents T : T[U], U : string)) { Contents recordContent; CsvRecord!(T, ErrorLevel, Range, Separator) recordRange; @@ -882,7 +882,7 @@ public: { header ~= col; auto ptr = col in colToIndex; - if(ptr) + if (ptr) *ptr = colIndex; colIndex++; } @@ -894,26 +894,29 @@ public: foreach(h; colHeaders) { immutable index = colToIndex[h]; - static if(ErrorLevel != Malformed.ignore) - if(index == size_t.max) + static if (ErrorLevel != Malformed.ignore) + if (index == size_t.max) throw new HeaderMismatchException ("Header not found: " ~ to!string(h)); indices[i++] = index; } - static if(!is(Contents == struct) && !is(Contents == class)) + static if (!is(Contents == struct) && !is(Contents == class)) { - static if(is(Contents T : T[U], U : string)) + static if (is(Contents T : T[U], U : string)) { + import std.algorithm : sort; sort(indices); } - else static if(ErrorLevel == Malformed.ignore) + else static if (ErrorLevel == Malformed.ignore) { + import std.algorithm : sort; sort(indices); } else { - if(!isSorted(indices)) + import std.algorithm : isSorted, findAdjacent; + if (!isSorted(indices)) { auto ex = new HeaderMismatchException ("Header in input does not match specified header."); @@ -946,11 +949,11 @@ public: @property auto front() { assert(!empty); - static if(is(Contents == struct) || is(Contents == class)) + static if (is(Contents == struct) || is(Contents == class)) { return recordContent; } - else static if(is(Contents T : T[U], U : string)) + else static if (is(Contents T : T[U], U : string)) { return recordContent; } @@ -984,25 +987,25 @@ public: recordRange.popFront(); } - static if(ErrorLevel == Malformed.throwException) - if(_input.rowLength == 0) + static if (ErrorLevel == Malformed.throwException) + if (_input.rowLength == 0) _input.rowLength = _input.col; _input.col = 0; - if(!_input.range.empty) + if (!_input.range.empty) { - if(_input.range.front == '\r') + if (_input.range.front == '\r') { _input.range.popFront(); - if(_input.range.front == '\n') + if (_input.range.front == '\n') _input.range.popFront(); } - else if(_input.range.front == '\n') + else if (_input.range.front == '\n') _input.range.popFront(); } - if(_input.range.empty) + if (_input.range.empty) { _empty = true; return; @@ -1013,10 +1016,10 @@ public: private void prime() { - if(_empty) + if (_empty) return; _input.row++; - static if(is(Contents == struct) || is(Contents == class)) + static if (is(Contents == struct) || is(Contents == class)) { recordRange = typeof(recordRange) (_input, _separator, _quote, null); @@ -1027,7 +1030,7 @@ public: (_input, _separator, _quote, indices); } - static if(is(Contents T : T[U], U : string)) + static if (is(Contents T : T[U], U : string)) { T[U] aa; try @@ -1044,9 +1047,9 @@ public: recordContent = aa; } - else static if(is(Contents == struct) || is(Contents == class)) + else static if (is(Contents == struct) || is(Contents == class)) { - static if(is(Contents == class)) + static if (is(Contents == class)) recordContent = new typeof(recordContent)(); else recordContent = typeof(recordContent).init; @@ -1057,11 +1060,11 @@ public: { auto colData = recordRange.front; scope(exit) colIndex++; - if(indices.length > 0) + if (indices.length > 0) { foreach(ti, ToType; FieldTypeTuple!(Contents)) { - if(indices[ti] == colIndex) + if (indices[ti] == colIndex) { static if (!isSomeString!ToType) skipWS(colData); recordContent.tupleof[ti] = to!ToType(colData); @@ -1072,7 +1075,7 @@ public: { foreach(ti, ToType; FieldTypeTuple!(Contents)) { - if(ti == colIndex) + if (ti == colIndex) { static if (!isSomeString!ToType) skipWS(colData); recordContent.tupleof[ti] = to!ToType(colData); @@ -1090,8 +1093,11 @@ public: } } +/// @safe pure unittest { + import std.algorithm; + string str = `76;^26^;22`; int[] ans = [76,26,22]; auto records = CsvReader!(int,Malformed.ignore,string,char,string[]) @@ -1108,8 +1114,9 @@ public: * requested $(D Contents) type is neither a structure or an associative array. */ private struct CsvRecord(Contents, Malformed ErrorLevel, Range, Separator) - if(!is(Contents == class) && !is(Contents == struct)) + if (!is(Contents == class) && !is(Contents == struct)) { + import std.array : appender; private: Input!(Range, ErrorLevel)* _input; Separator _separator; @@ -1141,12 +1148,12 @@ public: // how many will be skipped to get to the next header column size_t normalizer; foreach(ref c; _popCount) { - static if(ErrorLevel == Malformed.ignore) + static if (ErrorLevel == Malformed.ignore) { // If we are not throwing exceptions // a header may not exist, indices are sorted // and will be size_t.max if not found. - if(c == size_t.max) + if (c == size_t.max) break; } c -= normalizer; @@ -1179,7 +1186,7 @@ public: */ private bool recordEnd() { - if(_input.range.empty + if (_input.range.empty || _input.range.front == '\n' || _input.range.front == '\r') { @@ -1200,28 +1207,30 @@ public: */ void popFront() { + static if (ErrorLevel == Malformed.throwException) + import std.format : format; // Skip last of record when header is depleted. - if(_popCount.ptr && _popCount.empty) + if (_popCount.ptr && _popCount.empty) while(!recordEnd()) { prime(1); } - if(recordEnd()) + if (recordEnd()) { _empty = true; - static if(ErrorLevel == Malformed.throwException) - if(_input.rowLength != 0) - if(_input.col != _input.rowLength) + static if (ErrorLevel == Malformed.throwException) + if (_input.rowLength != 0) + if (_input.col != _input.rowLength) throw new CSVException( format("Row %s's length %s does not match "~ "previous length of %s.", _input.row, _input.col, _input.rowLength)); return; } else { - static if(ErrorLevel == Malformed.throwException) - if(_input.rowLength != 0) - if(_input.col > _input.rowLength) + static if (ErrorLevel == Malformed.throwException) + if (_input.rowLength != 0) + if (_input.col > _input.rowLength) throw new CSVException( format("Row %s's length %s does not match "~ "previous length of %s.", _input.row, @@ -1231,7 +1240,7 @@ public: // Separator is left on the end of input from the last call. // This cannot be moved to after the call to csvNextToken as // there may be an empty record after it. - if(_input.range.front == _separator) + if (_input.range.front == _separator) _input.range.popFront(); _front.shrinkTo(0); @@ -1248,7 +1257,7 @@ public: { _input.col++; _front.shrinkTo(0); - if(_input.range.front == _separator) + if (_input.range.front == _separator) _input.range.popFront(); try @@ -1285,17 +1294,17 @@ public: } auto skipNum = _popCount.empty ? 0 : _popCount.front; - if(!_popCount.empty) + if (!_popCount.empty) _popCount.popFront(); - if(skipNum == size_t.max) { + if (skipNum == size_t.max) { while(!recordEnd()) prime(1); _empty = true; return; } - if(skipNum) + if (skipNum) prime(skipNum); auto data = _front.data; @@ -1315,28 +1324,6 @@ public: * start with either a delimiter or record break (\n, \r\n, \r) which * must be removed for subsequent calls. * - * ------- - * string str = "65,63\n123,3673"; - * - * auto a = appender!(char[])(); - * - * csvNextToken(str,a,',','"'); - * assert(a.data == "65"); - * assert(str == ",63\n123,3673"); - * - * str.popFront(); - * a.shrinkTo(0); - * csvNextToken(str,a,',','"'); - * assert(a.data == "63"); - * assert(str == "\n123,3673"); - * - * str.popFront(); - * a.shrinkTo(0); - * csvNextToken(str,a,',','"'); - * assert(a.data == "123"); - * assert(str == ",3673"); - * ------- - * * params: * input = Any CSV input * ans = The first field in the input @@ -1355,21 +1342,21 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, (ref Range input, ref Output ans, Separator sep, Separator quote, bool startQuoted = false) - if(isSomeChar!Separator && isInputRange!Range - && is(ElementType!Range == dchar) + if (isSomeChar!Separator && isInputRange!Range + && is(Unqual!(ElementType!Range) == dchar) && isOutputRange!(Output, dchar)) { bool quoted = startQuoted; bool escQuote; - if(input.empty) + if (input.empty) return; - if(input.front == '\n') + if (input.front == '\n') return; - if(input.front == '\r') + if (input.front == '\r') return; - if(input.front == quote) + if (input.front == quote) { quoted = true; input.popFront(); @@ -1378,25 +1365,25 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, while(!input.empty) { assert(!(quoted && escQuote)); - if(!quoted) + if (!quoted) { // When not quoted the token ends at sep - if(input.front == sep) + if (input.front == sep) break; - if(input.front == '\r') + if (input.front == '\r') break; - if(input.front == '\n') + if (input.front == '\n') break; } - if(!quoted && !escQuote) + if (!quoted && !escQuote) { - if(input.front == quote) + if (input.front == quote) { // Not quoted, but quote found - static if(ErrorLevel == Malformed.throwException) + static if (ErrorLevel == Malformed.throwException) throw new IncompleteCellException( "Quote located in unquoted token"); - else static if(ErrorLevel == Malformed.ignore) + else static if (ErrorLevel == Malformed.ignore) ans.put(quote); } else @@ -1407,7 +1394,7 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, } else { - if(input.front == quote) + if (input.front == quote) { // Quoted, quote found // By turning off quoted and turning on escQuote @@ -1416,7 +1403,7 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, // quote or is followed by a non-quote (see outside else). // They are mutually exclusive, but provide different // information. - if(escQuote) + if (escQuote) { escQuote = false; quoted = true; @@ -1430,13 +1417,13 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, else { // Quoted, non-quote character - if(escQuote) + if (escQuote) { - static if(ErrorLevel == Malformed.throwException) + static if (ErrorLevel == Malformed.throwException) throw new IncompleteCellException( "Content continues after end quote, " ~ "or needs to be escaped."); - else static if(ErrorLevel == Malformed.ignore) + else static if (ErrorLevel == Malformed.ignore) break; } ans.put(input.front); @@ -1445,16 +1432,43 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, input.popFront(); } - static if(ErrorLevel == Malformed.throwException) - if(quoted && (input.empty || input.front == '\n' || input.front == '\r')) + static if (ErrorLevel == Malformed.throwException) + if (quoted && (input.empty || input.front == '\n' || input.front == '\r')) throw new IncompleteCellException( "Data continues on future lines or trailing quote"); } +/// +unittest +{ + import std.array : appender; + string str = "65,63\n123,3673"; + + auto a = appender!(char[])(); + + csvNextToken(str,a,',','"'); + assert(a.data == "65"); + assert(str == ",63\n123,3673"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "63"); + assert(str == "\n123,3673"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "123"); + assert(str == ",3673"); +} + // Test csvNextToken on simplest form and correct format. @safe pure unittest { + import std.array; + string str = "\U00010143Hello,65,63.63\nWorld,123,3673.562"; auto a = appender!(dchar[])(); @@ -1496,6 +1510,8 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, // Test quoted tokens @safe pure unittest { + import std.array; + string str = `one,two,"three ""quoted""","",` ~ "\"five\nnew line\"\nsix"; auto a = appender!(dchar[])(); @@ -1537,6 +1553,8 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, // Test empty data is pulled at end of record. @safe pure unittest { + import std.array; + string str = "one,"; auto a = appender!(dchar[])(); csvNextToken(str,a,',','"'); @@ -1551,6 +1569,8 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, // Test exceptions @safe pure unittest { + import std.array; + string str = "\"one\nnew line"; typeof(appender!(dchar[])()) a; @@ -1594,6 +1614,8 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, // Test modifying token delimiter @safe pure unittest { + import std.array; + string str = `one|two|/three "quoted"/|//`; auto a = appender!(dchar[])(); diff --git a/std/datetime.d b/std/datetime.d index 4b9a7a0f35b..8e4ed3b28b4 100644 --- a/std/datetime.d +++ b/std/datetime.d @@ -58,7 +58,7 @@ time as a $(LREF SysTime). To print it, $(D toString) is sufficient, but if using $(D toISOString), $(D toISOExtString), or $(D toSimpleString), use the corresponding $(D fromISOString), - $(D fromISOExtString), or $(D fromISOExtString) to create a + $(D fromISOExtString), or $(D fromSimpleString) to create a $(LREF SysTime) from the string. -------------------- @@ -110,50 +110,29 @@ public import core.time; import core.exception; import core.stdc.time; -import std.array; -import std.algorithm; -import std.ascii; -import std.conv; import std.exception; -import std.file; -import std.functional; -import std.math; -import std.path; -import std.range; -import std.stdio; -import std.string; -import std.system; +import std.range.primitives; import std.traits; -import std.typecons; -import std.utf; +// FIXME +import std.functional; //: unaryFun; version(Windows) { import core.sys.windows.windows; - import std.c.windows.winsock; + import core.sys.windows.winsock2; import std.windows.registry; } else version(Posix) { - import core.sys.posix.arpa.inet; import core.sys.posix.stdlib; - import core.sys.posix.time; import core.sys.posix.sys.time; } version(unittest) { - import core.stdc.string; import std.stdio; - import std.typetuple; } -//I'd just alias it to indexOf, but -//http://d.puremagic.com/issues/show_bug.cgi?id=6013 would mean that that would -//pollute the global namespace. So, for now, I've created an alias which is -//highly unlikely to conflict with anything that anyone else is doing. -private alias std.string.indexOf stds_indexOf; - unittest { initializeTests(); @@ -425,7 +404,7 @@ public: Throws: $(LREF DateTimeException) if it fails to get the time. +/ - static @property TickDuration currSystemTick() @safe + static @property TickDuration currSystemTick() @safe nothrow { return TickDuration.currSystemTick; } @@ -517,6 +496,8 @@ private: +/ struct SysTime { + import std.typecons : Rebindable; + public: /++ @@ -538,6 +519,7 @@ public: unittest { + import std.format : format; static void test(DateTime dt, immutable TimeZone tz, long expected) { auto sysTime = SysTime(dt, tz); @@ -589,6 +571,7 @@ public: unittest { + import std.format : format; static void test(DateTime dt, Duration fracSecs, immutable TimeZone tz, long expected) { auto sysTime = SysTime(dt, fracSecs, tz); @@ -651,6 +634,8 @@ public: /+deprecated+/ unittest { + import std.format : format; + static void test(DateTime dt, FracSec fracSec, immutable TimeZone tz, @@ -703,6 +688,7 @@ public: { static void test(Date d, immutable TimeZone tz, long expected) { + import std.format : format; auto sysTime = SysTime(d, tz); assert(sysTime._stdTime == expected); assert(sysTime._timezone is (tz is null ? LocalTime() : tz), @@ -741,6 +727,7 @@ public: { static void test(long stdTime, immutable TimeZone tz) { + import std.format : format; auto sysTime = SysTime(stdTime, tz); assert(sysTime._stdTime == stdTime); assert(sysTime._timezone is (tz is null ? LocalTime() : tz), @@ -758,7 +745,7 @@ public: Params: rhs = The $(LREF SysTime) to assign to this one. +/ - ref SysTime opAssign(const ref SysTime rhs) @safe pure nothrow + ref SysTime opAssign(const ref SysTime rhs) return @safe pure nothrow { _stdTime = rhs._stdTime; _timezone = rhs._timezone; @@ -770,7 +757,7 @@ public: Params: rhs = The $(LREF SysTime) to assign to this one. +/ - ref SysTime opAssign(SysTime rhs) @safe pure nothrow + ref SysTime opAssign(SysTime rhs) return @safe pure nothrow { _stdTime = rhs._stdTime; _timezone = rhs._timezone; @@ -798,6 +785,7 @@ public: unittest { + import std.range; assert(SysTime(DateTime.init, UTC()) == SysTime(0, UTC())); assert(SysTime(DateTime.init, UTC()) == SysTime(0)); assert(SysTime(Date.init, UTC()) == SysTime(0)); @@ -863,6 +851,7 @@ public: unittest { + import std.range; assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0, UTC())) == 0); assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0)) == 0); assert(SysTime(Date.init, UTC()).opCmp(SysTime(0)) == 0); @@ -930,8 +919,10 @@ public: unittest { + import std.range; static void test(SysTime sysTime, long expected) { + import std.format : format; assert(sysTime.year == expected, format("Value given: %s", sysTime)); } @@ -1002,6 +993,7 @@ public: unittest { + import std.range; static void test(SysTime st, int year, in SysTime expected) { st.year = year; @@ -1068,6 +1060,7 @@ public: unittest { + import std.format : format; foreach(st; testSysTimesBC) { auto msg = format("SysTime: %s", st); @@ -1126,8 +1119,10 @@ public: unittest { + import std.range; static void test(SysTime st, int year, in SysTime expected) { + import std.format : format; st.yearBC = year; assert(st == expected, format("SysTime: %s", st)); } @@ -1198,8 +1193,10 @@ public: unittest { + import std.range; static void test(SysTime sysTime, Month expected) { + import std.format : format; assert(sysTime.month == expected, format("Value given: %s", sysTime)); } @@ -1261,6 +1258,8 @@ public: unittest { + import std.range; + static void test(SysTime st, Month month, in SysTime expected) { st.month = cast(Month)month; @@ -1354,8 +1353,11 @@ public: unittest { + import std.range; + static void test(SysTime sysTime, int expected) { + import std.format : format; assert(sysTime.day == expected, format("Value given: %s", sysTime)); } @@ -1418,6 +1420,9 @@ public: unittest { + import std.format : format; + import std.range; + foreach(day; chain(testDays)) { foreach(st; chain(testSysTimesBC, testSysTimesAD)) @@ -1504,6 +1509,9 @@ public: unittest { + import std.range; + import std.format : format; + static void test(SysTime sysTime, int expected) { assert(sysTime.hour == expected, @@ -1578,6 +1586,9 @@ public: unittest { + import std.range; + import std.format : format; + foreach(hour; chain(testHours)) { foreach(st; chain(testSysTimesBC, testSysTimesAD)) @@ -1623,6 +1634,9 @@ public: unittest { + import std.range; + import std.format : format; + static void test(SysTime sysTime, int expected) { assert(sysTime.minute == expected, @@ -1700,6 +1714,9 @@ public: unittest { + import std.range; + import std.format : format; + foreach(minute; testMinSecs) { foreach(st; chain(testSysTimesBC, testSysTimesAD)) @@ -1746,6 +1763,9 @@ public: unittest { + import std.range; + import std.format : format; + static void test(SysTime sysTime, int expected) { assert(sysTime.second == expected, @@ -1825,6 +1845,9 @@ public: unittest { + import std.range; + import std.format : format; + foreach(second; testMinSecs) { foreach(st; chain(testSysTimesBC, testSysTimesAD)) @@ -1878,6 +1901,8 @@ public: unittest { + import std.range; + assert(SysTime(0, UTC()).fracSecs == Duration.zero); assert(SysTime(1, UTC()).fracSecs == hnsecs(1)); assert(SysTime(-1, UTC()).fracSecs == hnsecs(9_999_999)); @@ -1966,6 +1991,9 @@ public: unittest { + import std.range; + import std.format : format; + foreach(fracSec; testFracSecs) { foreach(st; chain(testSysTimesBC, testSysTimesAD)) @@ -2016,6 +2044,9 @@ public: /+deprecated+/ unittest { + import std.range; + import std.format : format; + static void test(SysTime sysTime, FracSec expected, size_t line = __LINE__) { if(sysTime.fracSec != expected) @@ -2103,6 +2134,9 @@ public: /+deprecated+/ unittest { + import std.range; + import std.format : format; + foreach(fracSec; testFracSecs) { foreach(st; chain(testSysTimesBC, testSysTimesAD)) @@ -2403,9 +2437,10 @@ public: version(Posix) { + import std.utf : toUTFz; timeInfo.tm_gmtoff = cast(int)convert!("hnsecs", "seconds")(adjTime - _stdTime); - auto zone = (timeInfo.tm_isdst ? _timezone.dstName : _timezone.stdName).dup; - timeInfo.tm_zone = zone.toUTFz!(const(char)*)(); + auto zone = (timeInfo.tm_isdst ? _timezone.dstName : _timezone.stdName); + timeInfo.tm_zone = zone.toUTFz!(char*)(); } return timeInfo; @@ -2413,6 +2448,7 @@ public: unittest { + import std.conv : to; version(Posix) { scope(exit) clearTZEnvVar(); @@ -4899,6 +4935,7 @@ public: { static void testST(SysTime orig, int hours, in SysTime expected, size_t line = __LINE__) { + import std.format : format; orig.roll!"hours"(hours); if(orig != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -5116,6 +5153,7 @@ public: { static void testST(SysTime orig, int minutes, in SysTime expected, size_t line = __LINE__) { + import std.format : format; orig.roll!"minutes"(minutes); if(orig != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -5326,6 +5364,7 @@ public: { static void testST(SysTime orig, int seconds, in SysTime expected, size_t line = __LINE__) { + import std.format : format; orig.roll!"seconds"(seconds); if(orig != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -5545,6 +5584,7 @@ public: { static void testST(SysTime orig, int milliseconds, in SysTime expected, size_t line = __LINE__) { + import std.format : format; orig.roll!"msecs"(milliseconds); if(orig != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -5650,6 +5690,7 @@ public: { static void testST(SysTime orig, long microseconds, in SysTime expected, size_t line = __LINE__) { + import std.format : format; orig.roll!"usecs"(microseconds); if(orig != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -5779,6 +5820,7 @@ public: { static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__) { + import std.format : format; orig.roll!"hnsecs"(hnsecs); if(orig != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -5936,6 +5978,8 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; + SysTime retval = SysTime(this._stdTime, this._timezone); static if(is(Unqual!D == Duration)) @@ -6003,6 +6047,7 @@ public: static void testST(in SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__) { + import std.format : format; auto result = orig + dur!"hnsecs"(hnsecs); if(result != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", result, expected), __FILE__, line); @@ -6153,6 +6198,8 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; + static if(is(Unqual!D == Duration)) auto hnsecs = duration.total!"hnsecs"; else static if(is(Unqual!D == TickDuration)) @@ -6203,6 +6250,8 @@ public: static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__) { + import std.format : format; + auto r = orig += dur!"hnsecs"(hnsecs); if(orig != expected) throw new AssertError(format("Failed 1. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -6804,6 +6853,8 @@ public: { void test(Date date, SysTime st, size_t line = __LINE__) { + import std.format : format; + if(date.dayOfGregorianCal != st.dayOfGregorianCal) { throw new AssertError(format("Date [%s] SysTime [%s]", date.dayOfGregorianCal, st.dayOfGregorianCal), @@ -7019,6 +7070,8 @@ public: { void testST(SysTime orig, int day, in SysTime expected, size_t line = __LINE__) { + import std.format : format; + orig.dayOfGregorianCal = day; if(orig != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); @@ -7056,6 +7109,8 @@ public: void testST2(int day, in SysTime expected, size_t line = __LINE__) { + import std.format : format; + st.dayOfGregorianCal = day; if(st != expected) throw new AssertError(format("Failed. actual [%s] != expected [%s]", st, expected), __FILE__, line); @@ -7665,6 +7720,7 @@ public: +/ string toISOString() @safe const nothrow { + import std.format : format; try { immutable adjustedTime = adjTime; @@ -7794,6 +7850,7 @@ public: +/ string toISOExtString() @safe const nothrow { + import std.format : format; try { immutable adjustedTime = adjTime; @@ -7927,6 +7984,7 @@ public: +/ string toSimpleString() @safe const nothrow { + import std.format : format; try { immutable adjustedTime = adjTime; @@ -8094,10 +8152,15 @@ public: static SysTime fromISOString(S)(in S isoString, immutable TimeZone tz = null) @safe if(isSomeString!S) { + import std.string : strip; + import std.conv : to; + import std.algorithm : startsWith, find; + import std.format : format; + auto dstr = to!dstring(strip(isoString)); - immutable skipFirst = dstr.startsWith("+", "-") != 0; + immutable skipFirst = dstr.startsWith('+', '-') != 0; - auto found = (skipFirst ? dstr[1..$] : dstr).find(".", "Z", "+", "-"); + auto found = (skipFirst ? dstr[1..$] : dstr).find('.', 'Z', '+', '-'); auto dateTimeStr = dstr[0 .. $ - found[0].length]; dstring fracSecStr; @@ -8107,7 +8170,7 @@ public: { if(found[1] == 1) { - auto foundTZ = found[0].find("Z", "+", "-"); + auto foundTZ = found[0].find('Z', '+', '-'); if(foundTZ[1] != 0) { @@ -8310,12 +8373,17 @@ public: static SysTime fromISOExtString(S)(in S isoExtString, immutable TimeZone tz = null) @safe if(isSomeString!(S)) { + import std.string : strip; + import std.conv : to; + import std.algorithm : countUntil, find; + import std.format : format; + auto dstr = to!dstring(strip(isoExtString)); - auto tIndex = dstr.stds_indexOf("T"); + auto tIndex = dstr.countUntil('T'); enforce(tIndex != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); - auto found = dstr[tIndex + 1 .. $].find(".", "Z", "+", "-"); + auto found = dstr[tIndex + 1 .. $].find('.', 'Z', '+', '-'); auto dateTimeStr = dstr[0 .. $ - found[0].length]; dstring fracSecStr; @@ -8325,7 +8393,7 @@ public: { if(found[1] == 1) { - auto foundTZ = found[0].find("Z", "+", "-"); + auto foundTZ = found[0].find('Z', '+', '-'); if(foundTZ[1] != 0) { @@ -8531,12 +8599,17 @@ public: static SysTime fromSimpleString(S)(in S simpleString, immutable TimeZone tz = null) @safe if(isSomeString!(S)) { + import std.string : strip; + import std.conv : to; + import std.algorithm : countUntil, find; + import std.format : format; + auto dstr = to!dstring(strip(simpleString)); - auto spaceIndex = dstr.stds_indexOf(" "); + auto spaceIndex = dstr.countUntil(' '); enforce(spaceIndex != -1, new DateTimeException(format("Invalid Simple String: %s", simpleString))); - auto found = dstr[spaceIndex + 1 .. $].find(".", "Z", "+", "-"); + auto found = dstr[spaceIndex + 1 .. $].find('.', 'Z', '+', '-'); auto dateTimeStr = dstr[0 .. $ - found[0].length]; dstring fracSecStr; @@ -8546,7 +8619,7 @@ public: { if(found[1] == 1) { - auto foundTZ = found[0].find("Z", "+", "-"); + auto foundTZ = found[0].find('Z', '+', '-'); if(foundTZ[1] != 0) { @@ -8789,7 +8862,7 @@ private: /++ Represents a date in the - $(WEB http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic Gregorian Calendar) + $(WEB en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic Gregorian Calendar) ranging from 32,768 B.C. to 32,767 A.D. Positive years are A.D. Non-positive years are B.C. @@ -9020,6 +9093,8 @@ public: unittest { + import std.range; + //Test A.D. foreach(gd; chain(testGregDaysBC, testGregDaysAD)) assert(Date(gd.day) == gd.date); @@ -9228,6 +9303,8 @@ public: +/ @property ushort yearBC() @safe const pure { + import std.format : format; + if(isAD) throw new DateTimeException(format("Year %s is A.D.", _year)); return cast(ushort)((_year * -1) + 1); @@ -9382,6 +9459,9 @@ public: unittest { + import std.range; + import std.format : format; + static void test(Date date, int expected) { assert(date.day == expected, @@ -11183,6 +11263,8 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; + Date retval = this; static if(is(Unqual!D == Duration)) @@ -11281,6 +11363,8 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; + static if(is(Unqual!D == Duration)) immutable days = duration.total!"days"; else static if(is(Unqual!D == TickDuration)) @@ -11708,6 +11792,8 @@ public: unittest { + import std.range; + foreach(year; filter!((a){return !yearIsLeapYear(a);}) (chain(testYearsBC, testYearsAD))) { @@ -11865,6 +11951,8 @@ public: unittest { + import std.range; + foreach(gd; chain(testGregDaysBC, testGregDaysAD)) assert(gd.date.dayOfGregorianCal == gd.day); @@ -12229,6 +12317,7 @@ public: +/ string toISOString() @safe const pure nothrow { + import std.format : format; try { if(_year >= 0) @@ -12284,6 +12373,7 @@ public: +/ string toISOExtString() @safe const pure nothrow { + import std.format : format; try { if(_year >= 0) @@ -12339,6 +12429,7 @@ public: +/ string toSimpleString() @safe const pure nothrow { + import std.format : format; try { if(_year >= 0) @@ -12423,6 +12514,12 @@ public: static Date fromISOString(S)(in S isoString) @safe pure if(isSomeString!S) { + import std.ascii : isDigit; + import std.string : strip; + import std.conv : to; + import std.algorithm : all, startsWith; + import std.format : format; + auto dstr = to!dstring(strip(isoString)); enforce(dstr.length >= 8, new DateTimeException(format("Invalid ISO String: %s", isoString))); @@ -12436,7 +12533,7 @@ public: if(year.length > 4) { - enforce(year.startsWith("-") || year.startsWith("+"), + enforce(year.startsWith('-', '+'), new DateTimeException(format("Invalid ISO String: %s", isoString))); enforce(all!isDigit(year[1..$]), new DateTimeException(format("Invalid ISO String: %s", isoString))); @@ -12539,6 +12636,12 @@ public: static Date fromISOExtString(S)(in S isoExtString) @safe pure if(isSomeString!(S)) { + import std.ascii : isDigit; + import std.string : strip; + import std.conv : to; + import std.algorithm : all, startsWith; + import std.format : format; + auto dstr = to!dstring(strip(isoExtString)); enforce(dstr.length >= 10, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); @@ -12556,7 +12659,7 @@ public: if(year.length > 4) { - enforce(year.startsWith("-") || year.startsWith("+"), + enforce(year.startsWith('-', '+'), new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); enforce(all!isDigit(year[1..$]), new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); @@ -12660,6 +12763,12 @@ public: static Date fromSimpleString(S)(in S simpleString) @safe pure if(isSomeString!(S)) { + import std.ascii : isDigit; + import std.string : strip; + import std.conv : to; + import std.algorithm : all, startsWith; + import std.format : format; + auto dstr = to!dstring(strip(simpleString)); enforce(dstr.length >= 11, new DateTimeException(format("Invalid string format: %s", simpleString))); @@ -12674,7 +12783,7 @@ public: if(year.length > 4) { - enforce(year.startsWith("-") || year.startsWith("+"), + enforce(year.startsWith('-', '+'), new DateTimeException(format("Invalid string format: %s", simpleString))); enforce(all!isDigit(year[1..$]), new DateTimeException(format("Invalid string format: %s", simpleString))); @@ -12839,7 +12948,7 @@ private: Params: days = The number of days to add to this Date. +/ - ref Date _addDays(long days) @safe pure nothrow + ref Date _addDays(long days) return @safe pure nothrow { dayOfGregorianCal = cast(int)(dayOfGregorianCal + days); return this; @@ -13006,6 +13115,7 @@ private: @safe pure invariant() { + import std.format : format; assert(valid!"months"(_month), format("Invariant Failure: year [%s] month [%s] day [%s]", _year, _month, _day)); assert(valid!"days"(_year, _month, _day), @@ -13368,6 +13478,8 @@ public: if(units == "minutes" || units == "seconds") { + import std.format : format; + enum memberVarStr = units[0 .. $ - 1]; value %= 60; mixin(format("auto newVal = cast(ubyte)(_%s) + value;", memberVarStr)); @@ -13562,6 +13674,8 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; + TimeOfDay retval = this; static if(is(Unqual!D == Duration)) @@ -13654,6 +13768,7 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; static if(is(Unqual!D == Duration)) immutable hnsecs = duration.total!"hnsecs"; else static if(is(Unqual!D == TickDuration)) @@ -13762,6 +13877,7 @@ public: +/ string toISOString() @safe const pure nothrow { + import std.format : format; try return format("%02d%02d%02d", _hour, _minute, _second); catch(Exception e) @@ -13791,6 +13907,7 @@ public: +/ string toISOExtString() @safe const pure nothrow { + import std.format : format; try return format("%02d:%02d:%02d", _hour, _minute, _second); catch(Exception e) @@ -13848,6 +13965,12 @@ public: static TimeOfDay fromISOString(S)(in S isoString) @safe pure if(isSomeString!S) { + import std.ascii : isDigit; + import std.string : strip; + import std.conv : to; + import std.algorithm : all; + import std.format : format; + auto dstr = to!dstring(strip(isoString)); enforce(dstr.length == 6, new DateTimeException(format("Invalid ISO String: %s", isoString))); @@ -13950,6 +14073,12 @@ public: static TimeOfDay fromISOExtString(S)(in S isoExtString) @safe pure if(isSomeString!S) { + import std.ascii : isDigit; + import std.string : strip; + import std.conv : to; + import std.algorithm : all; + import std.format : format; + auto dstr = to!dstring(strip(isoExtString)); enforce(dstr.length == 8, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); @@ -14094,7 +14223,7 @@ private: Params: seconds = The number of seconds to add to this TimeOfDay. +/ - ref TimeOfDay _addSeconds(long seconds) @safe pure nothrow + ref TimeOfDay _addSeconds(long seconds) return @safe pure nothrow { long hnsecs = convert!("seconds", "hnsecs")(seconds); hnsecs += convert!("hours", "hnsecs")(_hour); @@ -14204,6 +14333,7 @@ private: @safe pure invariant() { + import std.format : format; assert(_valid(_hour, _minute, _second), format("Invariant Failure: hour [%s] minute [%s] second [%s]", _hour, _minute, _second)); } @@ -14835,6 +14965,9 @@ public: unittest { + import std.range; + import std.format : format; + static void test(DateTime dateTime, int expected) { assert(dateTime.day == expected, format("Value given: %s", dateTime)); @@ -15818,6 +15951,8 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; + DateTime retval = this; static if(is(Unqual!D == Duration)) @@ -15914,6 +16049,8 @@ public: (is(Unqual!D == Duration) || is(Unqual!D == TickDuration))) { + import std.format : format; + DateTime retval = this; static if(is(Unqual!D == Duration)) @@ -16512,6 +16649,7 @@ public: +/ string toISOString() @safe const pure nothrow { + import std.format : format; try return format("%sT%s", _date.toISOString(), _tod.toISOString()); catch(Exception e) @@ -16564,6 +16702,7 @@ public: +/ string toISOExtString() @safe const pure nothrow { + import std.format : format; try return format("%sT%s", _date.toISOExtString(), _tod.toISOExtString()); catch(Exception e) @@ -16615,6 +16754,7 @@ public: +/ string toSimpleString() @safe const pure nothrow { + import std.format : format; try return format("%s %s", _date.toSimpleString(), _tod.toString()); catch(Exception e) @@ -16695,10 +16835,15 @@ public: static DateTime fromISOString(S)(in S isoString) @safe pure if(isSomeString!S) { + import std.string : strip; + import std.conv : to; + import std.algorithm : countUntil; + import std.format : format; + immutable dstr = to!dstring(strip(isoString)); enforce(dstr.length >= 15, new DateTimeException(format("Invalid ISO String: %s", isoString))); - auto t = dstr.stds_indexOf('T'); + auto t = dstr.countUntil('T'); enforce(t != -1, new DateTimeException(format("Invalid ISO String: %s", isoString))); @@ -16778,10 +16923,15 @@ public: static DateTime fromISOExtString(S)(in S isoExtString) @safe pure if(isSomeString!(S)) { + import std.string : strip; + import std.conv : to; + import std.algorithm : countUntil; + import std.format : format; + immutable dstr = to!dstring(strip(isoExtString)); enforce(dstr.length >= 15, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); - auto t = dstr.stds_indexOf('T'); + auto t = dstr.countUntil('T'); enforce(t != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); @@ -16859,10 +17009,15 @@ public: static DateTime fromSimpleString(S)(in S simpleString) @safe pure if(isSomeString!(S)) { + import std.string : strip; + import std.conv : to; + import std.algorithm : countUntil; + import std.format : format; + immutable dstr = to!dstring(strip(simpleString)); enforce(dstr.length >= 15, new DateTimeException(format("Invalid string format: %s", simpleString))); - auto t = dstr.stds_indexOf(' '); + auto t = dstr.countUntil(' '); enforce(t != -1, new DateTimeException(format("Invalid string format: %s", simpleString))); @@ -16990,7 +17145,7 @@ private: Params: seconds = The number of seconds to add to this $(LREF DateTime). +/ - ref DateTime _addSeconds(long seconds) @safe pure nothrow + ref DateTime _addSeconds(long seconds) return @safe pure nothrow { long hnsecs = convert!("seconds", "hnsecs")(seconds); hnsecs += convert!("hours", "hnsecs")(_tod._hour); @@ -17369,7 +17524,7 @@ assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).length == dur!"days"(5903)); -------------------- +/ - @property typeof(end - begin) length() const pure nothrow + @property auto length() const pure nothrow { return _end - _begin; } @@ -17836,6 +17991,8 @@ assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( +/ Interval intersection(in Interval interval) const { + import std.format : format; + enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval))); auto begin = _begin > interval._begin ? _begin : interval._begin; @@ -17868,6 +18025,8 @@ assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( +/ Interval intersection(in PosInfInterval!TP interval) const { + import std.format : format; + enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval))); return Interval(_begin > interval._begin ? _begin : interval._begin, _end); @@ -17897,6 +18056,8 @@ assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( +/ Interval intersection(in NegInfInterval!TP interval) const { + import std.format : format; + enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval))); return Interval(_begin, _end < interval._end ? _end : interval._end); @@ -18011,6 +18172,8 @@ assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( +/ Interval merge(in Interval interval) const { + import std.format : format; + enforce(this.isAdjacent(interval) || this.intersects(interval), new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); @@ -18044,6 +18207,8 @@ assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( +/ PosInfInterval!TP merge(in PosInfInterval!TP interval) const { + import std.format : format; + enforce(this.isAdjacent(interval) || this.intersects(interval), new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); @@ -18074,6 +18239,8 @@ assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( +/ NegInfInterval!TP merge(in NegInfInterval!TP interval) const { + import std.format : format; + enforce(this.isAdjacent(interval) || this.intersects(interval), new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); @@ -18647,6 +18814,7 @@ private: +/ string _toStringImpl() const nothrow { + import std.format : format; try return format("[%s - %s)", _begin, _end); catch(Exception e) @@ -20578,6 +20746,8 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( +/ Interval!TP intersection(in Interval!TP interval) const { + import std.format : format; + enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval))); auto begin = _begin > interval._begin ? _begin : interval._begin; @@ -20631,6 +20801,8 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( +/ Interval!TP intersection(in NegInfInterval!TP interval) const { + import std.format : format; + enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval))); return Interval!TP(_begin, interval._end); @@ -20740,6 +20912,8 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( +/ PosInfInterval merge(in Interval!TP interval) const { + import std.format : format; + enforce(this.isAdjacent(interval) || this.intersects(interval), new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); @@ -21114,6 +21288,7 @@ private: +/ string _toStringImpl() const nothrow { + import std.format : format; try return format("[%s - ∞)", _begin); catch(Exception e) @@ -22772,6 +22947,8 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( +/ Interval!TP intersection(in Interval!TP interval) const { + import std.format : format; + enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval))); auto end = _end < interval._end ? _end : interval._end; @@ -22802,6 +22979,8 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( +/ Interval!TP intersection(in PosInfInterval!TP interval) const { + import std.format : format; + enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval))); return Interval!TP(interval._begin, _end); @@ -22940,6 +23119,8 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( +/ NegInfInterval merge(in Interval!TP interval) const { + import std.format : format; + enforce(this.isAdjacent(interval) || this.intersects(interval), new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); @@ -23312,6 +23493,7 @@ private: +/ string _toStringImpl() const nothrow { + import std.format : format; try return format("[-∞ - %s)", _end); catch(Exception e) @@ -25161,6 +25343,8 @@ private: +/ void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const { + import std.format : format; + static if(dir == Direction.fwd) { enforce(newTP > _interval._begin, @@ -25600,6 +25784,8 @@ private: +/ void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const { + import std.format : format; + enforce(newTP > _interval._begin, new DateTimeException(format("Generated time point is before previous begin: prev [%s] new [%s]", interval._begin, @@ -25699,6 +25885,7 @@ unittest //Test PosInfIntervalRange's popFront(). unittest { + import std.range; auto range = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes); auto expected = range.front; @@ -25885,6 +26072,8 @@ private: +/ void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const { + import std.format : format; + enforce(newTP < _interval._end, new DateTimeException(format("Generated time point is before previous end: prev [%s] new [%s]", interval._end, @@ -25983,6 +26172,8 @@ unittest //Test NegInfIntervalRange's popFront(). unittest { + import std.range; + auto range = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes); auto expected = range.front; @@ -26179,7 +26370,42 @@ auto tz = TimeZone.getTimeZone("America/Los_Angeles"); version(Posix) return PosixTimeZone.getTimeZone(name); else version(Windows) - return WindowsTimeZone.getTimeZone(tzDatabaseNameToWindowsTZName(name)); + { + import std.format : format; + auto windowsTZName = tzDatabaseNameToWindowsTZName(name); + if(windowsTZName != null) + { + try + return WindowsTimeZone.getTimeZone(windowsTZName); + catch(DateTimeException dte) + { + auto oldName = _getOldName(windowsTZName); + if(oldName != null) + return WindowsTimeZone.getTimeZone(oldName); + throw dte; + } + } + else + throw new DateTimeException(format("%s does not have an equivalent Windows time zone.", name)); + } + } + + // The purpose of this is to handle the case where a Windows time zone is + // new and exists on an up-to-date Windows box but does not exist on Windows + // boxes which have not been properly updated. The "date added" is included + // on the theory that we'll be able to remove them at some point in the + // the future once enough time has passed, and that way, we know how much + // time has passed. + private static string _getOldName(string windowsTZName) @safe pure nothrow + { + switch(windowsTZName) + { + case "Belarus Standard Time": return "Kaliningrad Standard Time"; // Added 2014-10-08 + case "Russia Time Zone 10": return "Magadan Standard Time"; // Added 2014-10-08 + case "Russia Time Zone 11": return "Magadan Standard Time"; // Added 2014-10-08 + case "Russia Time Zone 3": return "Russian Standard Time"; // Added 2014-10-08 + default: return null; + } } //Since reading in the time zone files could be expensive, most unit tests @@ -26187,6 +26413,12 @@ auto tz = TimeZone.getTimeZone("America/Los_Angeles"); //reads a time zone file. unittest { + import std.path : buildPath; + import std.file : exists, isFile; + import std.conv : to; + import std.format : format; + + version(Posix) scope(exit) clearTZEnvVar(); static immutable(TimeZone) testTZ(string tzName, @@ -26488,6 +26720,10 @@ auto tz = TimeZone.getTimeZone("America/Los_Angeles"); return PosixTimeZone.getInstalledTZNames(subName); else version(Windows) { + import std.array : appender; + import std.algorithm : startsWith, sort; + import std.format : format; + auto windowsNames = WindowsTimeZone.getInstalledTZNames(); auto retval = appender!(string[])(); @@ -26496,7 +26732,10 @@ auto tz = TimeZone.getTimeZone("America/Los_Angeles"); auto tzName = windowsTZNameToTZDatabaseName(winName); version(unittest) + { + import std.string; assert(tzName !is null, format("TZName which is missing: %s", winName)); + } if(tzName !is null && tzName.startsWith(subName)) retval.put(tzName); @@ -26610,6 +26849,7 @@ public: { version(Posix) { + import std.conv : to; try return to!string(tzname[0]); catch(Exception e) @@ -26680,6 +26920,7 @@ public: { version(Posix) { + import std.conv : to; try return to!string(tzname[1]); catch(Exception e) @@ -26936,6 +27177,8 @@ public: unittest { + import std.format : format; + assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0); assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0); @@ -27402,6 +27645,8 @@ private: +/ static string toISOString(Duration utcOffset) @safe pure { + import std.format : format; + immutable absOffset = abs(utcOffset); enforce!DateTimeException(absOffset < dur!"minutes"(1440), "Offset from UTC must be within range (-24:00 - 24:00)."); @@ -27455,16 +27700,22 @@ private: static immutable(SimpleTimeZone) fromISOString(S)(S isoString) @safe pure if(isSomeString!S) { + import std.ascii : isDigit; + import std.string : strip; + import std.conv : to; + import std.algorithm : startsWith, countUntil, all; + import std.format : format; + auto dstr = to!dstring(strip(isoString)); - enforce(dstr.startsWith("-") || dstr.startsWith("+"), new DateTimeException("Invalid ISO String")); + enforce(dstr.startsWith('-', '+'), new DateTimeException("Invalid ISO String")); - auto sign = dstr.startsWith("-") ? -1 : 1; + auto sign = dstr.startsWith('-') ? -1 : 1; dstr.popFront(); enforce(!dstr.empty, new DateTimeException("Invalid ISO String")); - immutable colon = dstr.stds_indexOf(":"); + immutable colon = dstr.countUntil(':'); dstring hoursStr; dstring minutesStr; @@ -27646,6 +27897,11 @@ private: +/ final class PosixTimeZone : TimeZone { + import std.stdio : File; + import std.path : buildNormalizedPath, extension; + import std.file : isDir, isFile, exists, dirEntries, SpanMode, DirEntry; + import std.string : strip, representation; + import std.algorithm : countUntil, canFind, startsWith; public: /++ @@ -27809,6 +28065,10 @@ assert(tz.dstName == "PDT"); // directory. static immutable(PosixTimeZone) getTimeZone(string name, string tzDatabaseDir = defaultTZDatabaseDir) @trusted { + import std.algorithm : sort; + import std.range : retro; + import std.format : format; + name = strip(name); enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); @@ -28034,7 +28294,7 @@ assert(tz.dstName == "PDT"); tempTTInfo.tt_gmtoff = -tempTTInfo.tt_gmtoff; auto abbrevChars = tzAbbrevChars[tempTTInfo.tt_abbrind .. $]; - string abbrev = abbrevChars[0 .. abbrevChars.stds_indexOf("\0")].idup; + string abbrev = abbrevChars[0 .. abbrevChars.countUntil('\0')].idup; ttInfo = new immutable(TTInfo)(tempTTInfos[i], abbrev); } @@ -28123,10 +28383,18 @@ assert(tz.dstName == "PDT"); +/ static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = defaultTZDatabaseDir) @trusted { + import std.array : appender; + import std.algorithm : sort; + import std.format : format; + version(Posix) subName = strip(subName); else version(Windows) + { + import std.array : replace; + import std.path : dirSeparator; subName = replace(strip(subName), "/", dirSeparator); + } enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir))); @@ -28553,6 +28821,11 @@ else version(Windows) { final class WindowsTimeZone : TimeZone { + import std.format : format; + import std.conv : to; + import std.algorithm : sort; + import std.array : appender; + public: @property override bool hasDST() @safe const nothrow @@ -28581,6 +28854,8 @@ else version(Windows) static immutable(WindowsTimeZone) getTimeZone(string name) @trusted { + import std.utf : toUTF16; + scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`); foreach (tzKeyName; baseKey.keyNames) @@ -28888,6 +29163,7 @@ else version(Posix) void setTZEnvVar(string tzDatabaseName) @trusted nothrow { import std.internal.cstring : tempCString; + import std.path : buildNormalizedPath; try { @@ -28949,7 +29225,6 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Africa/Banjul": return "Greenwich Standard Time"; case "Africa/Bissau": return "Greenwich Standard Time"; case "Africa/Blantyre": return "South Africa Standard Time"; - case "Africa/Bogota": return "Line Islands Standard Time"; case "Africa/Brazzaville": return "W. Central Africa Standard Time"; case "Africa/Bujumbura": return "South Africa Standard Time"; case "Africa/Cairo": return "Egypt Standard Time"; @@ -29043,7 +29318,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "America/Glace_Bay": return "Atlantic Standard Time"; case "America/Godthab": return "Greenland Standard Time"; case "America/Goose_Bay": return "Atlantic Standard Time"; - case "America/Grand_Turk": return "Eastern Standard Time"; + case "America/Grand_Turk": return "SA Western Standard Time"; case "America/Grenada": return "SA Western Standard Time"; case "America/Guadeloupe": return "SA Western Standard Time"; case "America/Guatemala": return "Central America Standard Time"; @@ -29150,7 +29425,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Asia/Aden": return "Arab Standard Time"; case "Asia/Almaty": return "Central Asia Standard Time"; case "Asia/Amman": return "Jordan Standard Time"; - case "Asia/Anadyr": return "Magadan Standard Time"; + case "Asia/Anadyr": return "Russia Time Zone 11"; case "Asia/Aqtau": return "West Asia Standard Time"; case "Asia/Aqtobe": return "West Asia Standard Time"; case "Asia/Ashgabat": return "West Asia Standard Time"; @@ -29162,7 +29437,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Asia/Bishkek": return "Central Asia Standard Time"; case "Asia/Brunei": return "Singapore Standard Time"; case "Asia/Calcutta": return "India Standard Time"; - case "Asia/Chita": return "Yakutsk Standard Time"; + case "Asia/Chita": return "North Asia East Standard Time"; case "Asia/Choibalsan": return "Ulaanbaatar Standard Time"; case "Asia/Colombo": return "Sri Lanka Standard Time"; case "Asia/Damascus": return "Syria Standard Time"; @@ -29177,7 +29452,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Asia/Jayapura": return "Tokyo Standard Time"; case "Asia/Jerusalem": return "Israel Standard Time"; case "Asia/Kabul": return "Afghanistan Standard Time"; - case "Asia/Kamchatka": return "Magadan Standard Time"; + case "Asia/Kamchatka": return "Russia Time Zone 11"; case "Asia/Karachi": return "Pakistan Standard Time"; case "Asia/Katmandu": return "Nepal Standard Time"; case "Asia/Khandyga": return "Yakutsk Standard Time"; @@ -29191,7 +29466,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Asia/Manila": return "Singapore Standard Time"; case "Asia/Muscat": return "Arabian Standard Time"; case "Asia/Nicosia": return "GTB Standard Time"; - case "Asia/Novokuznetsk": return "N. Central Asia Standard Time"; + case "Asia/Novokuznetsk": return "North Asia Standard Time"; case "Asia/Novosibirsk": return "N. Central Asia Standard Time"; case "Asia/Omsk": return "N. Central Asia Standard Time"; case "Asia/Oral": return "West Asia Standard Time"; @@ -29208,7 +29483,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Asia/Seoul": return "Korea Standard Time"; case "Asia/Shanghai": return "China Standard Time"; case "Asia/Singapore": return "Singapore Standard Time"; - case "Asia/Srednekolymsk": return "Magadan Standard Time"; + case "Asia/Srednekolymsk": return "Russia Time Zone 10"; case "Asia/Taipei": return "Taipei Standard Time"; case "Asia/Tashkent": return "West Asia Standard Time"; case "Asia/Tbilisi": return "Georgian Standard Time"; @@ -29261,6 +29536,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Etc/GMT-11": return "Central Pacific Standard Time"; case "Etc/GMT-12": return "UTC+12"; case "Etc/GMT-13": return "Tonga Standard Time"; + case "Etc/GMT-14": return "Line Islands Standard Time"; case "Etc/GMT-2": return "South Africa Standard Time"; case "Etc/GMT-3": return "E. Africa Standard Time"; case "Etc/GMT-4": return "Arabian Standard Time"; @@ -29297,7 +29573,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Europe/Madrid": return "Romance Standard Time"; case "Europe/Malta": return "W. Europe Standard Time"; case "Europe/Mariehamn": return "FLE Standard Time"; - case "Europe/Minsk": return "Kaliningrad Standard Time"; + case "Europe/Minsk": return "Belarus Standard Time"; case "Europe/Monaco": return "W. Europe Standard Time"; case "Europe/Moscow": return "Russian Standard Time"; case "Europe/Oslo": return "W. Europe Standard Time"; @@ -29306,7 +29582,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Europe/Prague": return "Central Europe Standard Time"; case "Europe/Riga": return "FLE Standard Time"; case "Europe/Rome": return "W. Europe Standard Time"; - case "Europe/Samara": return "Russian Standard Time"; + case "Europe/Samara": return "Russia Time Zone 3"; case "Europe/San_Marino": return "W. Europe Standard Time"; case "Europe/Sarajevo": return "Central European Standard Time"; case "Europe/Simferopol": return "Russian Standard Time"; @@ -29350,6 +29626,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc case "Pacific/Guam": return "West Pacific Standard Time"; case "Pacific/Honolulu": return "Hawaiian Standard Time"; case "Pacific/Johnston": return "Hawaiian Standard Time"; + case "Pacific/Kiritimati": return "Line Islands Standard Time"; case "Pacific/Kosrae": return "Central Pacific Standard Time"; case "Pacific/Kwajalein": return "UTC+12"; case "Pacific/Majuro": return "UTC+12"; @@ -29375,6 +29652,7 @@ string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc version(Windows) unittest { + import std.format : format; foreach(tzName; TimeZone.getInstalledTZNames()) assert(tzDatabaseNameToWindowsTZName(tzName) !is null, format("TZName which failed: %s", tzName)); } @@ -29410,6 +29688,7 @@ string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc case "Azores Standard Time": return "Atlantic/Azores"; case "Bahia Standard Time": return "America/Bahia"; case "Bangladesh Standard Time": return "Asia/Dhaka"; + case "Belarus Standard Time": return "Europe/Minsk"; case "Canada Central Standard Time": return "America/Regina"; case "Cape Verde Standard Time": return "Atlantic/Cape_Verde"; case "Caucasus Standard Time": return "Asia/Yerevan"; @@ -29451,7 +29730,7 @@ string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc case "Kamchatka Standard Time": return "Asia/Kamchatka"; case "Korea Standard Time": return "Asia/Seoul"; case "Libya Standard Time": return "Africa/Tripoli"; - case "Line Islands Standard Time": return "Africa/Bogota"; + case "Line Islands Standard Time": return "Pacific/Kiritimati"; case "Magadan Standard Time": return "Asia/Magadan"; case "Mauritius Standard Time": return "Indian/Mauritius"; // Same as with E. Europe Standard Time. @@ -29479,6 +29758,9 @@ string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc case "Pakistan Standard Time": return "Asia/Karachi"; case "Paraguay Standard Time": return "America/Asuncion"; case "Romance Standard Time": return "Europe/Paris"; + case "Russia Time Zone 10": return "Asia/Srednekolymsk"; + case "Russia Time Zone 11": return "Asia/Anadyr"; + case "Russia Time Zone 3": return "Europe/Samara"; case "Russian Standard Time": return "Europe/Moscow"; case "SA Eastern Standard Time": return "America/Cayenne"; case "SA Pacific Standard Time": return "America/Bogota"; @@ -29515,6 +29797,7 @@ string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc version(Windows) unittest { + import std.format : format; foreach(tzName; WindowsTimeZone.getInstalledTZNames()) assert(windowsTZNameToTZDatabaseName(tzName) !is null, format("TZName which failed: %s", tzName)); } @@ -29839,6 +30122,7 @@ TickDuration[fun.length] benchmark(fun...)(uint n) /// unittest { + import std.conv : to; int a; void f0() {} void f1() {auto b = a;} @@ -30026,6 +30310,7 @@ static bool yearIsLeapYear(int year) @safe pure nothrow unittest { + import std.format : format; foreach(year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999, 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011]) { @@ -30503,7 +30788,7 @@ unittest /++ The given array of $(D char) or random-access range of $(D char) or $(D ubyte) is expected to be in the format specified in - $(WEB http://tools.ietf.org/html/rfc5322, RFC 5322) section 3.3 with the + $(WEB tools.ietf.org/html/rfc5322, RFC 5322) section 3.3 with the grammar rule $(I date-time). It is the date-time format commonly used in internet messages such as e-mail and HTTP. The corresponding $(LREF SysTime) will be returned. @@ -30536,6 +30821,7 @@ unittest +/ SysTime parseRFC822DateTime()(in char[] value) @safe { + import std.string : representation; return parseRFC822DateTime(value.representation); } @@ -30544,6 +30830,13 @@ SysTime parseRFC822DateTime(R)(R value) @safe if(isRandomAccessRange!R && hasSlicing!R && hasLength!R && (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte))) { + import std.functional : not; + import std.ascii : isDigit; + import std.typecons : Rebindable; + import std.string : capitalize, format; + import std.conv : to; + import std.algorithm : find, all; + void stripAndCheckLen(R valueBefore, size_t minLen, size_t line = __LINE__) { value = _stripCFWS(valueBefore); @@ -30751,6 +31044,8 @@ unittest version(unittest) void testParse822(alias cr)(string str, SysTime expected, size_t line = __LINE__) { + import std.string; + import std.format : format; auto value = cr(str); auto result = parseRFC822DateTime(value); if(result != expected) @@ -30768,6 +31063,14 @@ version(unittest) void testBadParse822(alias cr)(string str, size_t line = __LIN unittest { + import std.algorithm; + import std.ascii; + import std.format : format; + import std.range; + import std.string; + import std.typecons; + import std.typetuple; + static struct Rand3Letters { enum empty = false; @@ -30775,7 +31078,6 @@ unittest void popFront() { import std.random; - alias std.ascii.letters letters; _mon = rndGen.map!(a => letters[a % letters.length])().take(3).array().assumeUnique(); } string _mon; @@ -30786,7 +31088,7 @@ unittest function(string a){return cast(ubyte[])a;}, function(string a){return a;}, function(string a){return map!(b => cast(char)b)(a.representation);})) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 scope(failure) writeln(typeof(cr).stringof); alias testParse822!cr test; alias testBadParse822!cr testBad; @@ -31024,12 +31326,20 @@ unittest testBad(cast(string)currStr); testBad((cast(string)currStr) ~ " "); } - } + }(); } // Obsolete Format per section 4.3 of RFC 5322. unittest { + import std.algorithm; + import std.ascii; + import std.format : format; + import std.range; + import std.string; + import std.typecons; + import std.typetuple; + auto std1 = SysTime(DateTime(2012, 12, 21, 13, 14, 15), UTC()); auto std2 = SysTime(DateTime(2012, 12, 21, 13, 14, 0), UTC()); auto std3 = SysTime(DateTime(1912, 12, 21, 13, 14, 15), UTC()); @@ -31043,7 +31353,7 @@ unittest function(string a){return cast(ubyte[])a;}, function(string a){return a;}, function(string a){return map!(b => cast(char)b)(a.representation);})) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 scope(failure) writeln(typeof(cr).stringof); alias testParse822!cr test; { @@ -31230,7 +31540,7 @@ unittest assert(collectExceptionMsg!DateTimeException(parseRFC822DateTime(value)) == tooShortMsg); } } - } + }(); } @@ -31243,6 +31553,7 @@ unittest +/ bool validTimeUnits(string[] units...) @safe pure nothrow { + import std.algorithm : canFind; foreach(str; units) { if(!canFind(timeStrings[], str)) @@ -31270,7 +31581,10 @@ bool validTimeUnits(string[] units...) @safe pure nothrow +/ int cmpTimeUnits(string lhs, string rhs) @safe pure { - auto tstrings = timeStrings.dup; + import std.format : format; + import std.algorithm : countUntil; + + auto tstrings = timeStrings; immutable indexOfLHS = countUntil(tstrings, lhs); immutable indexOfRHS = countUntil(tstrings, rhs); @@ -31332,7 +31646,8 @@ template CmpTimeUnits(string lhs, string rhs) +/ private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow { - auto tstrings = timeStrings.dup; + import std.algorithm : countUntil; + auto tstrings = timeStrings; immutable indexOfLHS = countUntil(tstrings, lhs); immutable indexOfRHS = countUntil(tstrings, rhs); @@ -31346,6 +31661,11 @@ private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow unittest { + import std.format : format; + import std.string; + import std.typecons; + import std.typetuple; + static string genTest(size_t index) { auto currUnits = timeStrings[index]; @@ -31435,6 +31755,8 @@ void enforceValid(string units)(int value, string file = __FILE__, size_t line = units == "minutes" || units == "seconds") { + import std.format : format; + static if(units == "months") { if(!valid!units(value)) @@ -31475,6 +31797,7 @@ void enforceValid(string units) (int year, Month month, int day, string file = __FILE__, size_t line = __LINE__) @safe pure if(units == "days") { + import std.format : format; if(!valid!"days"(year, month, day)) throw new DateTimeException(format("%s is not a valid day in %s in %s", day, month, year), file, line); } @@ -31736,9 +32059,11 @@ unittest @safe unittest { + import std.math : isNaN; + @safe static void func(TickDuration td) { - assert(!td.to!("seconds", real)().isNaN); + assert(!td.to!("seconds", real)().isNaN()); } auto mt = measureTime!(func)(); @@ -31754,9 +32079,11 @@ unittest unittest { + import std.math : isNaN; + static void func(TickDuration td) { - assert(!td.to!("seconds", real)().isNaN); + assert(!td.to!("seconds", real)().isNaN()); } auto mt = measureTime!(func)(); @@ -32102,6 +32429,7 @@ unittest +/ string monthToString(Month month) @safe pure { + import std.format : format; assert(month >= Month.jan && month <= Month.dec, format("Invalid month: %s", month)); return _monthNames[month - Month.jan]; } @@ -32134,6 +32462,7 @@ unittest +/ Month monthFromString(string monthStr) @safe pure { + import std.format : format; switch(monthStr) { case "Jan": @@ -32189,7 +32518,8 @@ template nextSmallerTimeUnits(string units) if(validTimeUnits(units) && timeStrings.front != units) { - enum nextSmallerTimeUnits = timeStrings[countUntil(timeStrings.dup, units) - 1]; + import std.algorithm : countUntil; + enum nextSmallerTimeUnits = timeStrings[countUntil(timeStrings, units) - 1]; } unittest @@ -32214,7 +32544,8 @@ template nextLargerTimeUnits(string units) if(validTimeUnits(units) && timeStrings.back != units) { - enum nextLargerTimeUnits = timeStrings[countUntil(timeStrings.dup, units) + 1]; + import std.algorithm : countUntil; + enum nextLargerTimeUnits = timeStrings[countUntil(timeStrings, units) + 1]; } unittest @@ -32237,6 +32568,7 @@ unittest +/ static string fracSecsToISOString(int hnsecs) @safe pure nothrow { + import std.format : format; assert(hnsecs >= 0); try @@ -32290,6 +32622,11 @@ unittest static Duration fracSecsFromISOString(S)(in S isoString) @trusted pure if(isSomeString!S) { + import std.ascii : isDigit; + import std.string : representation; + import std.conv : to; + import std.algorithm : all; + if(isoString.empty) return Duration.zero; @@ -32452,9 +32789,14 @@ R _stripCFWS(R)(R range) unittest { + import std.algorithm; + import std.string; + import std.typecons; + import std.typetuple; + foreach(cr; TypeTuple!(function(string a){return cast(ubyte[])a;}, function(string a){return map!(b => cast(char)b)(a.representation);})) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 scope(failure) writeln(typeof(cr).stringof); assert(_stripCFWS(cr("")).empty); @@ -32542,7 +32884,7 @@ unittest assert(equal(_stripCFWS(cr(" \n (hello) \n (hello) \n \n hello")), cr("hello"))); assert(equal(_stripCFWS(cr(" \n \n (hello)\t\n (hello) \n hello")), cr("hello"))); assert(equal(_stripCFWS(cr(" \n\t\n\t(hello)\t\n (hello) \n hello")), cr("hello"))); - } + }(); } // This is so that we don't have to worry about std.conv.to throwing. It also @@ -32551,6 +32893,8 @@ unittest T _convDigits(T, R)(R str) if(isIntegral!T && isSigned!T) // The constraints on R were already covered by parseRFC822DateTime. { + import std.ascii : isDigit; + assert(!str.empty); T num = 0; foreach(i; 0 .. str.length) @@ -32566,6 +32910,8 @@ T _convDigits(T, R)(R str) unittest { + import std.conv : to; + import std.range; foreach(i; chain(iota(0, 101), [250, 999, 1000, 1001, 2345, 9999])) { scope(failure) writeln(i); @@ -32745,6 +33091,8 @@ unittest version(unittest) { + import std.typecons; + import std.algorithm; //Variables to help in testing. Duration currLocalDiffFromUTC; immutable (TimeZone)[] testTZs; @@ -33027,10 +33375,11 @@ version(unittest) : TimeZone.getTimeZone("America/Denver"); immutable ot = otherTZ.utcToTZ(0); - auto diffs = [0, lt, ot]; - auto diffAA = [0 : Rebindable!(immutable TimeZone)(UTC()), - lt : Rebindable!(immutable TimeZone)(LocalTime()), - ot : Rebindable!(immutable TimeZone)(otherTZ)]; + auto diffs = [0L, lt, ot]; + auto diffAA = [0L : Rebindable!(immutable TimeZone)(UTC())]; + diffAA[lt] = Rebindable!(immutable TimeZone)(LocalTime()); + diffAA[ot] = Rebindable!(immutable TimeZone)(otherTZ); + sort(diffs); testTZs = [diffAA[diffs[0]], diffAA[diffs[1]], diffAA[diffs[2]]]; diff --git a/std/demangle.d b/std/demangle.d index a6fbe090fc9..c90500a806b 100644 --- a/std/demangle.d +++ b/std/demangle.d @@ -7,9 +7,9 @@ * WIKI = Phobos/StdDemangle * * Copyright: Copyright Digital Mars 2000 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright), - * Thomas Kuehne, Frits van Bommel + * Thomas K$(UUML)hne, Frits van Bommel * Source: $(PHOBOSSRC std/_demangle.d) */ /* @@ -20,9 +20,7 @@ */ module std.demangle; -private import core.demangle; -private import std.exception; - +/+ private class MangleException : Exception { this() @@ -30,6 +28,7 @@ private class MangleException : Exception super("MangleException"); } } ++/ /***************************** * Demangle D mangled names. @@ -85,6 +84,8 @@ int main() string demangle(string name) { - auto ret = core.demangle.demangle(name); + import core.demangle : demangle; + import std.exception : assumeUnique; + auto ret = demangle(name); return assumeUnique(ret); } diff --git a/std/digest/crc.d b/std/digest/crc.d index b4a0c45a2e2..e92ff83af80 100644 --- a/std/digest/crc.d +++ b/std/digest/crc.d @@ -1,6 +1,9 @@ /** - +Cyclic Redundancy Check (32-bit) implementation. +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) @@ -11,9 +14,9 @@ $(TR $(TDNW OOP API) $(TD $(MYREF CRC32Digest)) ) $(TR $(TDNW Helpers) $(TD $(MYREF crcHexString) $(MYREF crc32Of)) ) +) ) - * Cyclic Redundancy Check (32-bit) implementation. * * This module conforms to the APIs defined in $(D std.digest.digest). To understand the * differences between the template and the OOP API, see $(D std.digest.digest). @@ -27,7 +30,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF crcHexString) $(MYREF crc32Of)) * to specify decreasing order for the correct result. The $(LREF crcHexString) alias can also * be used for this purpose. * - * License: Boost License 1.0 + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * * Authors: Pavel "EvilOne" Minayev, Alex Rønne Petersen, Johannes Pfau * @@ -38,7 +41,6 @@ $(TR $(TDNW Helpers) $(TD $(MYREF crcHexString) $(MYREF crc32Of)) * * Macros: * WIKI = Phobos/StdUtilDigestCRC32 - * MYREF = $1  * * Standards: * Implements the 'common' IEEE CRC32 variant @@ -63,7 +65,6 @@ public import std.digest.digest; version(unittest) import std.exception; -import std.bitmanip; /// unittest @@ -219,6 +220,7 @@ struct CRC32 */ ubyte[4] peek() const @safe pure nothrow @nogc { + import std.bitmanip : nativeToLittleEndian; //Complement, LSB first / Little Endian, see http://rosettacode.org/wiki/CRC-32 return nativeToLittleEndian(~_state); } diff --git a/std/digest/digest.d b/std/digest/digest.d index 40642bbdfed..2f9522bad92 100644 --- a/std/digest/digest.d +++ b/std/digest/digest.d @@ -1,6 +1,10 @@ /** - + * This module describes the digest APIs used in Phobos. All digests follow these APIs. + * Additionally, this module contains useful helper methods which can be used with every _digest type. + * +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) @@ -15,11 +19,9 @@ $(TR $(TDNW Helper functions) $(TD $(MYREF toHexString)) ) $(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDigest)) ) +) ) - * This module describes the digest APIs used in Phobos. All digests follow these APIs. - * Additionally, this module contains useful helper methods which can be used with every _digest type. - * * APIs: * There are two APIs for digests: The template API and the OOP API. The template API uses structs * and template helpers like $(LREF isDigest). The OOP API implements digests as classes inheriting @@ -39,14 +41,13 @@ $(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDi * In this simplest case, the template API can even be used without templates: Just use the "$(B x)" structs * directly. * - * License: Boost License 1.0 + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: * Johannes Pfau * * Source: $(PHOBOSSRC std/_digest/_digest.d) * * Macros: - * MYREF = $1  * MYREF2 = $1  * MYREF3 = $(D $1) * @@ -64,10 +65,11 @@ $(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDi */ module std.digest.digest; -import std.range, std.traits; +import std.traits; import std.typetuple : allSatisfy; public import std.ascii : LetterCase; + /// unittest { @@ -280,6 +282,7 @@ unittest */ template isDigest(T) { + import std.range : isOutputRange; enum bool isDigest = isOutputRange!(T, const(ubyte)[]) && isOutputRange!(T, ubyte) && is(T == struct) && is(typeof( @@ -387,6 +390,7 @@ unittest private template isDigestibleRange(Range) { import std.digest.md; + import std.range : isInputRange, ElementType; enum bool isDigestibleRange = isInputRange!Range && is(typeof( { MD5 ha; //Could use any conformant hash @@ -416,6 +420,7 @@ DigestType!Hash digest(Hash, Range)(auto ref Range range) if(!isArray!Range unittest { import std.digest.md; + import std.range : repeat; auto testRange = repeat!ubyte(cast(ubyte)'a', 100); auto md5 = digest!MD5(testRange); } @@ -472,6 +477,7 @@ char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, Rang unittest { import std.digest.md; + import std.range : repeat; auto testRange = repeat!ubyte(cast(ubyte)'a', 100); assert(hexDigest!MD5(testRange) == "36A92CC94A9E0FA21F625F8BFB007ADF"); } @@ -627,6 +633,7 @@ unittest unittest { + import std.range : isOutputRange; assert(!isDigest!(Digest)); assert(isOutputRange!(Digest, ubyte)); } @@ -736,6 +743,7 @@ string toHexString(Order order = Order.increasing, LetterCase letterCase = Lette } else { + import std.range : retro; foreach(u; retro(digest)) { result[i++] = hexDigits[u >> 4]; diff --git a/std/digest/md.d b/std/digest/md.d index ae25f4310d9..c2bb6f8b739 100644 --- a/std/digest/md.d +++ b/std/digest/md.d @@ -1,6 +1,10 @@ /** - + * Computes MD5 hashes of arbitrary data. MD5 hashes are 16 byte quantities that are like a + * checksum or CRC, but are more robust. + * +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) @@ -11,18 +15,16 @@ $(TR $(TDNW OOP API) $(TD $(MYREF MD5Digest)) ) $(TR $(TDNW Helpers) $(TD $(MYREF md5Of)) ) +) ) - * Computes MD5 hashes of arbitrary data. MD5 hashes are 16 byte quantities that are like a - * checksum or CRC, but are more robust. - * * This module conforms to the APIs defined in $(D std.digest.digest). To understand the * differences between the template and the OOP API, see $(D std.digest.digest). * * This module publicly imports $(D std.digest.digest) and can be used as a stand-alone * module. * - * License: Boost License 1.0 + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * * CTFE: * Digests do not work in CTFE @@ -38,7 +40,6 @@ $(TR $(TDNW Helpers) $(TD $(MYREF md5Of)) * * Macros: * WIKI = Phobos/StdMd5 - * MYREF = $1  */ /* md5.d - RSA Data Security, Inc., MD5 message-digest algorithm @@ -46,8 +47,6 @@ $(TR $(TDNW Helpers) $(TD $(MYREF md5Of)) */ module std.digest.md; -import std.bitmanip, std.exception, std.string; - public import std.digest.digest; /// @@ -362,6 +361,8 @@ struct MD5 */ ubyte[16] finish() @trusted pure nothrow @nogc { + import std.bitmanip : nativeToLittleEndian; + ubyte[16] data = void; ubyte[8] bits = void; uint index, padLen; @@ -556,7 +557,10 @@ unittest assert(result[0 .. 16] == result2 && result2 == cast(ubyte[])x"c3fcd3d76192e4007dfb496cca67e13b"); debug + { + import std.exception; assertThrown!Error(md5.finish(result[0 .. 15])); + } assert(md5.length == 16); diff --git a/std/digest/ripemd.d b/std/digest/ripemd.d index 6deb6775b5a..d8d50b521de 100644 --- a/std/digest/ripemd.d +++ b/std/digest/ripemd.d @@ -1,6 +1,10 @@ /** - + * Computes RIPEMD-160 hashes of arbitrary data. RIPEMD-160 hashes are 20 byte quantities + * that are like a checksum or CRC, but are more robust. + * +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) @@ -11,18 +15,16 @@ $(TR $(TDNW OOP API) $(TD $(MYREF RIPEMD160Digest)) ) $(TR $(TDNW Helpers) $(TD $(MYREF ripemd160Of)) ) +) ) - * Computes RIPEMD-160 hashes of arbitrary data. RIPEMD-160 hashes are 20 byte quantities - * that are like a checksum or CRC, but are more robust. - * * This module conforms to the APIs defined in $(D std.digest.digest). To understand the * differences between the template and the OOP API, see $(D std.digest.digest). * * This module publicly imports $(D std.digest.digest) and can be used as a stand-alone * module. * - * License: Boost License 1.0 + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * * CTFE: * Digests do not work in CTFE @@ -42,13 +44,10 @@ $(TR $(TDNW Helpers) $(TD $(MYREF ripemd160Of)) * * Macros: * WIKI = Phobos/StdRipemd - * MYREF = $1  */ module std.digest.ripemd; -import std.bitmanip, std.exception, std.string; - public import std.digest.digest; /// @@ -528,6 +527,8 @@ struct RIPEMD160 */ ubyte[20] finish() @trusted pure nothrow @nogc { + import std.bitmanip : nativeToLittleEndian; + ubyte[20] data = void; ubyte[8] bits = void; uint index, padLen; @@ -725,7 +726,10 @@ unittest assert(result[0 .. 20] == result2 && result2 == cast(ubyte[])x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); debug + { + import std.exception; assertThrown!Error(md.finish(result[0 .. 19])); + } assert(md.length == 20); diff --git a/std/digest/sha.d b/std/digest/sha.d index 8ff16d165e3..8b3137bfa13 100644 --- a/std/digest/sha.d +++ b/std/digest/sha.d @@ -1,7 +1,12 @@ // Written in the D programming language. /** - + * Computes SHA1 and SHA2 hashes of arbitrary data. SHA hashes are 20 to 64 byte + * quantities (depending on the SHA algorithm) that are like a checksum or CRC, + * but are more robust. + * +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) @@ -12,12 +17,9 @@ $(TR $(TDNW OOP API) $(TD $(MYREF SHA1Digest)) ) $(TR $(TDNW Helpers) $(TD $(MYREF sha1Of)) ) +) ) - * Computes SHA1 and SHA2 hashes of arbitrary data. SHA hashes are 20 to 64 byte - * quantities (depending on the SHA algorithm) that are like a checksum or CRC, - * but are more robust. - * * SHA2 comes in several different versions, all supported by this module: * SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224 and SHA-512/256. * @@ -27,7 +29,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF sha1Of)) * This module publicly imports $(D std.digest.digest) and can be used as a stand-alone * module. * - * License: Boost License 1.0 + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * * CTFE: * Digests do not work in CTFE @@ -48,7 +50,6 @@ $(TR $(TDNW Helpers) $(TD $(MYREF sha1Of)) * * Macros: * WIKI = Phobos/StdSha1 - * MYREF = $1  */ /* Copyright Kai Nacke 2012. @@ -119,9 +120,7 @@ else version(D_InlineAsm_X86_64) private version = USE_SSSE3; } -import std.ascii : hexDigits; -import std.exception : assumeUnique; -import core.bitop : bswap; +version(LittleEndian) import core.bitop : bswap; version(USE_SSSE3) import core.cpuid : hasSSSE3Support = ssse3; version(USE_SSSE3) import std.internal.digest.sha_SSSE3 : transformSSSE3; @@ -160,8 +159,8 @@ private ulong bigEndianToNative(ubyte[8] val) @trusted pure nothrow @nogc { version(LittleEndian) { - static import std.bitmanip; - return std.bitmanip.bigEndianToNative!ulong(val); + import std.bitmanip : bigEndianToNative; + return bigEndianToNative!ulong(val); } else return *cast(ulong*) &val; diff --git a/std/encoding.d b/std/encoding.d index e4e668d6fca..9e65ad74c56 100644 --- a/std/encoding.d +++ b/std/encoding.d @@ -39,7 +39,7 @@ Macros: WIKI=Phobos/StdEncoding Copyright: Copyright Janice Caron 2008 - 2009. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: Janice Caron Source: $(PHOBOSSRC std/_encoding.d) */ @@ -51,9 +51,8 @@ Distributed under the Boost Software License, Version 1.0. */ module std.encoding; -import std.string; import std.traits; -import std.range; +import std.range.primitives; unittest { @@ -1262,17 +1261,13 @@ bool isValidCodePoint(dchar c) explicitly specify the encoding type. Standards: Unicode 5.0, ASCII, ISO-8859-1, WINDOWS-1252 - - Examples: - ----------------------------------- - assert(encodingName!(Latin1Char) == "ISO-8859-1"); - ----------------------------------- */ @property string encodingName(T)() { return EncoderInstance!(T).encodingName; } +/// unittest { assert(encodingName!(char) == "UTF-8"); @@ -1291,22 +1286,19 @@ unittest explicitly specify the encoding type. Standards: Unicode 5.0, ASCII, ISO-8859-1, WINDOWS-1252 - - Examples: - ----------------------------------- - assert(canEncode!(Latin1Char)('A')); - ----------------------------------- */ bool canEncode(E)(dchar c) { return EncoderInstance!(E).canEncode(c); } +/// unittest { + assert( canEncode!(Latin1Char)('A')); assert(!canEncode!(AsciiChar)('\u00A0')); - assert(canEncode!(Latin1Char)('\u00A0')); - assert(canEncode!(Windows1252Char)('\u20AC')); + assert( canEncode!(Latin1Char)('\u00A0')); + assert( canEncode!(Windows1252Char)('\u20AC')); assert(!canEncode!(Windows1252Char)('\u20AD')); assert(!canEncode!(Windows1252Char)('\uFFFD')); assert(!canEncode!(char)(cast(dchar)0x110000)); @@ -1327,15 +1319,16 @@ bool isValidCodeUnit(E)(E c) return EncoderInstance!(E).isValidCodeUnit(c); } +/// unittest { - assert(!isValidCodeUnit(cast(AsciiChar)0xA0)); - assert( isValidCodeUnit(cast(Windows1252Char)0x80)); - assert(!isValidCodeUnit(cast(Windows1252Char)0x81)); assert(!isValidCodeUnit(cast(char)0xC0)); assert(!isValidCodeUnit(cast(char)0xFF)); assert( isValidCodeUnit(cast(wchar)0xD800)); assert(!isValidCodeUnit(cast(dchar)0xD800)); + assert(!isValidCodeUnit(cast(AsciiChar)0xA0)); + assert( isValidCodeUnit(cast(Windows1252Char)0x80)); + assert(!isValidCodeUnit(cast(Windows1252Char)0x81)); } /** @@ -1356,9 +1349,11 @@ bool isValid(E)(const(E)[] s) return s.length == validLength(s); } +/// unittest { - assert(isValid("\u20AC100")); + assert( isValid("\u20AC100")); + assert(!isValid(cast(char[3])[167, 133, 175])); } /** @@ -1437,6 +1432,7 @@ immutable(E)[] sanitize(E)(immutable(E)[] s) return cast(immutable(E)[])array[0..offset]; } +/// unittest { assert(sanitize("hello \xF0\x80world") == "hello \xEF\xBF\xBDworld"); @@ -1467,13 +1463,15 @@ body return before - s.length; } +/// unittest { assert(firstSequence("\u20AC1000") == "\u20AC".length); + assert(firstSequence("hel") == "h".length); } /** - Returns the length the last encoded sequence. + Returns the length of the last encoded sequence. The input to this function MUST be validly encoded. This is enforced by the function's in-contract. @@ -1496,9 +1494,11 @@ body return t.length - s.length; } +/// unittest { assert(lastSequence("1000\u20AC") == "\u20AC".length); + assert(lastSequence("hellö") == "ö".length); } /** @@ -1529,9 +1529,11 @@ body return t.length - s.length; } +/// unittest { assert(index("\u20AC100",1) == 3); + assert(index("hällo",2) == 3); } /** @@ -1742,7 +1744,6 @@ body Encodes $(D c) in units of type $(D E) and writes the result to the output range $(D R). Returns the number of $(D E)s written. */ - size_t encode(E, R)(dchar c, auto ref R range) if (isNativeOutputRange!(R, E)) { @@ -1750,28 +1751,28 @@ if (isNativeOutputRange!(R, E)) { if (c <= 0x7F) { - doPut(range, cast(char) c); + put(range, cast(char) c); return 1; } if (c <= 0x7FF) { - doPut(range, cast(char)(0xC0 | (c >> 6))); - doPut(range, cast(char)(0x80 | (c & 0x3F))); + put(range, cast(char)(0xC0 | (c >> 6))); + put(range, cast(char)(0x80 | (c & 0x3F))); return 2; } if (c <= 0xFFFF) { - doPut(range, cast(char)(0xE0 | (c >> 12))); - doPut(range, cast(char)(0x80 | ((c >> 6) & 0x3F))); - doPut(range, cast(char)(0x80 | (c & 0x3F))); + put(range, cast(char)(0xE0 | (c >> 12))); + put(range, cast(char)(0x80 | ((c >> 6) & 0x3F))); + put(range, cast(char)(0x80 | (c & 0x3F))); return 3; } if (c <= 0x10FFFF) { - doPut(range, cast(char)(0xF0 | (c >> 18))); - doPut(range, cast(char)(0x80 | ((c >> 12) & 0x3F))); - doPut(range, cast(char)(0x80 | ((c >> 6) & 0x3F))); - doPut(range, cast(char)(0x80 | (c & 0x3F))); + put(range, cast(char)(0xF0 | (c >> 18))); + put(range, cast(char)(0x80 | ((c >> 12) & 0x3F))); + put(range, cast(char)(0x80 | ((c >> 6) & 0x3F))); + put(range, cast(char)(0x80 | (c & 0x3F))); return 4; } else @@ -1783,16 +1784,16 @@ if (isNativeOutputRange!(R, E)) { if (c <= 0xFFFF) { - range.doPut(cast(wchar) c); + range.put(cast(wchar) c); return 1; } - range.doPut(cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800)); - range.doPut(cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00)); + range.put(cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800)); + range.put(cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00)); return 2; } else static if (is(Unqual!E == dchar)) { - range.doPut(c); + range.put(c); return 1; } else @@ -1800,8 +1801,10 @@ if (isNativeOutputRange!(R, E)) static assert(0); } } + unittest { + import std.array; Appender!(char[]) r; assert(encode!(char)('T', r) == 1); assert(encode!(wchar)('T', r) == 1); @@ -1840,6 +1843,26 @@ body EncoderInstance!(E).encode(c,dg); } +/** +Encodes the contents of $(D s) in units of type $(D Tgt), writing the result to an +output range. + +Returns: The number of $(D Tgt) elements written. +Params: +Tgt = Element type of $(D range). +s = Input array. +range = Output range. + */ +size_t encode(Tgt, Src, R)(in Src[] s, R range) +{ + size_t result; + foreach (c; s) + { + result += encode!(Tgt)(c, range); + } + return result; +} + /** Returns a foreachable struct which can bidirectionally iterate over all code points in a string. @@ -1882,6 +1905,7 @@ body return CodePoints!(E)(s); } +/// unittest { string s = "hello"; @@ -1910,19 +1934,6 @@ unittest Params: c = the code point to be encoded - - Examples: - -------------------------------------------------------- - dchar d = '\u20AC'; - foreach(c;codeUnits!(char)(d)) - { - writefln("%X",c) - } - // will print - // E2 - // 82 - // AC - -------------------------------------------------------- */ CodeUnits!(E) codeUnits(E)(dchar c) in @@ -1934,6 +1945,7 @@ body return CodeUnits!(E)(c); } +/// unittest { char[] a; @@ -1948,25 +1960,7 @@ unittest } /** -Encodes $(D c) in units of type $(D E) and writes the result to the -output range $(D R). Returns the number of $(D E)s written. - */ - -size_t encode(Tgt, Src, R)(in Src[] s, R range) -{ - size_t result; - foreach (c; s) - { - result += encode!(Tgt)(c, range); - } - return result; -} - -/** - Convert a string from one encoding to another. (See also to!() below). - - The input to this function MUST be validly encoded. - This is enforced by the function's in-contract. + Convert a string from one encoding to another. Supersedes: This function supersedes std.utf.toUTF8(), std.utf.toUTF16() and @@ -1976,19 +1970,12 @@ size_t encode(Tgt, Src, R)(in Src[] s, R range) Standards: Unicode 5.0, ASCII, ISO-8859-1, WINDOWS-1252 Params: - s = the source string - r = the destination string + s = Source string. $(B Must) be validly encoded. + This is enforced by the function's in-contract. + r = Destination string - Examples: - -------------------------------------------------------- - wstring ws; - transcode("hello world",ws); - // transcode from UTF-8 to UTF-16 - - Latin1String ls; - transcode(ws, ls); - // transcode from UTF-16 to ISO-8859-1 - -------------------------------------------------------- + See_Also: + $(XREF conv, to) */ void transcode(Src,Dst)(immutable(Src)[] s,out immutable(Dst)[] r) in @@ -2039,8 +2026,23 @@ body } } +/// +unittest +{ + wstring ws; + // transcode from UTF-8 to UTF-16 + transcode("hello world",ws); + assert(ws == "hello world"w); + + Latin1String ls; + // transcode from UTF-16 to ISO-8859-1 + transcode(ws, ls); + assert(ws == "hello world"); +} + unittest { + import std.range; import std.typetuple; { import std.conv : to; @@ -2077,41 +2079,6 @@ unittest } } -/* - Convert a string from one encoding to another. (See also transcode() above). - - The input to this function MUST be validly encoded. - This is enforced by the function's in-contract. - - Supersedes: - This function supersedes std.utf.toUTF8(), std.utf.toUTF16() and - std.utf.toUTF32(). - - Standards: Unicode 5.0, ASCII, ISO-8859-1, WINDOWS-1252 - - Params: - Dst = the destination encoding type - s = the source string - - Examples: - ----------------------------------------------------------------------------- - auto ws = to!(wchar)("hello world"); // transcode from UTF-8 to UTF-16 - auto ls = to!(Latin1Char)(ws); // transcode from UTF-16 to ISO-8859-1 - ----------------------------------------------------------------------------- - */ -// TODO: Commented out for no - to be moved to std.conv -// Dst to(Dst,Src)(immutable(Src)[] s) -// in -// { -// assert(isValid(s)); -// } -// body -// { -// Dst r; -// transcode(s,r); -// return r; -// } - //============================================================================= /** The base class for exceptions thrown by this module */ @@ -2125,6 +2092,8 @@ class UnrecognizedEncodingException : EncodingException /** Abstract base class of all encoding schemes */ abstract class EncodingScheme { + import std.uni : toLower; + /** * Registers a subclass of EncodingScheme. * @@ -2167,7 +2136,7 @@ abstract class EncodingScheme */ static EncodingScheme create(string encodingName) { - auto p = std.string.toLower(encodingName) in supported; + auto p = toLower(encodingName) in supported; if (p is null) throw new EncodingException("Unrecognized Encoding: "~encodingName); string className = *p; diff --git a/std/exception.d b/std/exception.d index dd669e9597c..4501d378d28 100644 --- a/std/exception.d +++ b/std/exception.d @@ -44,8 +44,11 @@ +/ module std.exception; -import std.array, std.conv, std.range, std.string, std.traits; -import core.exception, core.stdc.errno, core.stdc.string; +import std.range; +import std.traits; + +import core.stdc.errno; +import core.stdc.string; /++ Asserts that the given expression does $(I not) throw the given type @@ -74,22 +77,24 @@ void assertNotThrown(T : Throwable = Exception, E) string file = __FILE__, size_t line = __LINE__) { + import core.exception : AssertError; try { expression(); } catch (T t) { - immutable message = msg.empty ? t.msg : msg; - immutable tail = message.empty ? "." : ": " ~ message; - throw new AssertError(format("assertNotThrown failed: %s was thrown%s", - T.stringof, tail), - file, line, t); + immutable message = msg.length == 0 ? t.msg : msg; + immutable tail = message.length == 0 ? "." : ": " ~ message; + throw new AssertError("assertNotThrown failed: " ~ T.stringof ~ " was thrown" ~ tail, file, line, t); } } /// unittest { + import core.exception : AssertError; + + import std.string; assertNotThrown!StringException(enforce!StringException(true, "Error!")); //Exception is the default. @@ -101,6 +106,8 @@ unittest } unittest { + import core.exception : AssertError; + import std.string; assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( enforce!StringException(false, ""), "Error!")) == `assertNotThrown failed: StringException was thrown: Error!`); @@ -116,6 +123,8 @@ unittest unittest { + import core.exception : AssertError; + void throwEx(Throwable t) { throw t; } void nothrowEx() { } @@ -213,18 +222,22 @@ void assertThrown(T : Throwable = Exception, E) string file = __FILE__, size_t line = __LINE__) { + import core.exception : AssertError; + try expression(); catch (T) return; - - throw new AssertError(format("assertThrown failed: No %s was thrown%s%s", - T.stringof, msg.empty ? "." : ": ", msg), + throw new AssertError("assertThrown failed: No " ~ T.stringof ~ " was thrown" + ~ (msg.length == 0 ? "." : ": ") ~ msg, file, line); } /// unittest { + import core.exception : AssertError; + import std.string; + assertThrown!StringException(enforce!StringException(false, "Error!")); //Exception is the default. @@ -237,6 +250,8 @@ unittest unittest { + import core.exception : AssertError; + void throwEx(Throwable t) { throw t; } void nothrowEx() { } @@ -576,6 +591,8 @@ template enforceEx(E : Throwable) unittest { + import std.array : empty; + import core.exception : OutOfMemoryError; assertNotThrown(enforceEx!Exception(true)); assertNotThrown(enforceEx!Exception(true, "blah")); assertNotThrown(enforceEx!OutOfMemoryError(true)); @@ -720,6 +737,7 @@ unittest +/ string collectExceptionMsg(T = Exception, E)(lazy E expression) { + import std.array : empty; try { expression(); @@ -893,12 +911,14 @@ T assumeWontThrow(T)(lazy T expr, string file = __FILE__, size_t line = __LINE__) nothrow { + import core.exception : AssertError; try { return expr; } catch(Exception e) { + import std.range.primitives : empty; immutable tail = msg.empty ? "." : ": " ~ msg; throw new AssertError("assumeWontThrow failed: Expression did throw" ~ tail, file, line); @@ -933,6 +953,8 @@ unittest unittest { + import core.exception : AssertError; + void alwaysThrows() { throw new Exception("I threw up"); @@ -962,12 +984,12 @@ check if $(D source) points to $(D target), $(I not) what $(D target) references If $(D source) is or contains a union, then there may be either false positives or false negatives: -$(D doesPointTo) will return $(D true) if it is absolutly certain +$(D doesPointTo) will return $(D true) if it is absolutely certain $(D source) points to $(D target). It may produce false negatives, but never false positives. This function should be prefered when trying to validate input data. -$(D mayPointTo) will return $(D false) if it is absolutly certain +$(D mayPointTo) will return $(D false) if it is absolutely certain $(D source) does not point to $(D target). It may produce false positives, but never false negatives. This function should be prefered for defensively choosing a code path. @@ -1003,6 +1025,7 @@ bool doesPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) } else static if (isDynamicArray!S) { + import std.array : overlap; return overlap(cast(void[])source, cast(void[])(&target)[0 .. 1]).length != 0; } else @@ -1036,6 +1059,7 @@ bool mayPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) } else static if (isDynamicArray!S) { + import std.array : overlap; return overlap(cast(void[])source, cast(void[])(&target)[0 .. 1]).length != 0; } else @@ -1245,7 +1269,7 @@ unittest assert(!doesPointTo(darr[0 .. 1], darr)); //But they do point their elements - foreach(i; 0 .. 4) + foreach (i; 0 .. 4) assert(doesPointTo(darr, darr[i])); assert(doesPointTo(darr[0..3], darr[2])); assert(!doesPointTo(darr[0..3], darr[3])); @@ -1389,10 +1413,11 @@ unittest //more alias this opCast */ class ErrnoException : Exception { - uint errno; // operating system error code + final @property uint errno() { return _errno; } /// Operating system error code. + private uint _errno; this(string msg, string file = null, size_t line = 0) @trusted { - errno = .errno; + _errno = .errno; version (linux) { char[1024] buf = void; @@ -1402,7 +1427,7 @@ class ErrnoException : Exception { auto s = core.stdc.string.strerror(errno); } - super(msg~" ("~to!string(s)~")", file, line); + super(msg ~ " (" ~ s[0..s.strlen].idup ~ ")", file, line); } } @@ -1470,7 +1495,7 @@ class ErrnoException : Exception CommonType!(T1, T2) ifThrown(E : Throwable = Exception, T1, T2)(lazy scope T1 expression, lazy scope T2 errorHandler) { static assert(!is(typeof(return) == void), - "The error handler's return value("~T2.stringof~") does not have a common type with the expression("~T1.stringof~")."); + "The error handler's return value(" ~ T2.stringof ~ ") does not have a common type with the expression(" ~ T1.stringof ~ ")."); try { return expression(); @@ -1486,7 +1511,7 @@ CommonType!(T1, T2) ifThrown(E : Throwable = Exception, T1, T2)(lazy scope T1 ex CommonType!(T1, T2) ifThrown(E : Throwable, T1, T2)(lazy scope T1 expression, scope T2 delegate(E) errorHandler) { static assert(!is(typeof(return) == void), - "The error handler's return value("~T2.stringof~") does not have a common type with the expression("~T1.stringof~")."); + "The error handler's return value(" ~ T2.stringof ~ ") does not have a common type with the expression(" ~ T1.stringof ~ ")."); try { return expression(); @@ -1502,7 +1527,7 @@ CommonType!(T1, T2) ifThrown(E : Throwable, T1, T2)(lazy scope T1 expression, sc CommonType!(T1, T2) ifThrown(T1, T2)(lazy scope T1 expression, scope T2 delegate(Exception) errorHandler) { static assert(!is(typeof(return) == void), - "The error handler's return value("~T2.stringof~") does not have a common type with the expression("~T1.stringof~")."); + "The error handler's return value(" ~ T2.stringof ~ ") does not have a common type with the expression(" ~ T1.stringof ~ ")."); try { return expression(); @@ -1516,6 +1541,8 @@ CommonType!(T1, T2) ifThrown(T1, T2)(lazy scope T1 expression, scope T2 delegate //Verify Examples unittest { + import std.string; + import std.conv; //Revert to a default value upon an error: assert("x".to!int().ifThrown(0) == 0); @@ -1546,6 +1573,9 @@ unittest unittest { + import std.string; + import std.conv; + import core.exception; //Basic behaviour - all versions. assert("1".to!int().ifThrown(0) == 1); assert("x".to!int().ifThrown(0) == 0); @@ -1584,3 +1614,388 @@ version(unittest) package static assert({ cast(void)dg(); return true; }()); cast(void)dg(); } + +/** This $(D enum) is used to select the primitives of the range to handle by the + $(LREF handle) range wrapper. The values of the $(D enum) can be $(D OR)'d to + select multiple primitives to be handled. + */ +enum RangePrimitive +{ + front = 0b00_0000_0001, /// + back = 0b00_0000_0010, /// Ditto + popFront = 0b00_0000_0100, /// Ditto + popBack = 0b00_0000_1000, /// Ditto + empty = 0b00_0001_0000, /// Ditto + save = 0b00_0010_0000, /// Ditto + length = 0b00_0100_0000, /// Ditto + opDollar = 0b00_1000_0000, /// Ditto + opIndex = 0b01_0000_0000, /// Ditto + opSlice = 0b10_0000_0000, /// Ditto +} + +/** Handle exceptions thrown from range primitives. + +Use the $(LREF RangePrimitive) enum to specify which primitives to _handle. +Multiple range primitives can be handled at once by using the $(D OR) operator. +All handled primitives must have return types or values compatible with the +user-supplied handler. + +Params: + E = The type of $(D Throwable) to _handle. + primitivesToHandle = Set of range primitives to _handle. + handler = The callable that is called when a handled primitive throws a + $(D Throwable) of type $(D E). The handler must accept arguments of + the form $(D E, ref IRange) and its return value is used as the primitive's + return value whenever $(D E) is thrown. + input = The range to _handle. + +Returns: A wrapper $(D struct) that preserves the range interface of $(D input). + +opSlice: +Infinite ranges with slicing support must return an instance of +$(XREF range_package, Take) when sliced with a specific lower and upper +bound (see $(XREF range_primitives, hasSlicing)); $(D handle) deals with this +by $(D take)ing 0 from the return value of the handler function and returning +that when an exception is caught. +*/ +auto handle(E : Throwable, RangePrimitive primitivesToHandle, alias handler, IRange)(IRange input) +if (isInputRange!IRange) +{ + static struct Handler + { + IRange range; + alias range this; + + static if (isForwardRange!IRange) + { + static if (primitivesToHandle & RangePrimitive.save) + { + @property typeof(this) save() + { + try + { + return typeof(this)(range.save); + } + catch(E exception) + { + return typeof(this)(handler(exception, this.range)); + } + } + } + } + + static if (isInfinite!IRange) + { + enum bool empty = false; + } + else static if (primitivesToHandle & RangePrimitive.empty) + { + @property bool empty() + { + try + { + return this.range.empty; + } + catch(E exception) + { + return handler(exception, this.range); + } + } + } + + static if (isInputRange!IRange) + { + static if (primitivesToHandle & RangePrimitive.front) + { + @property auto ref front() + { + try + { + return this.range.front; + } + catch(E exception) + { + return handler(exception, this.range); + } + } + } + + static if (primitivesToHandle & RangePrimitive.popFront) + { + void popFront() + { + try + { + this.range.popFront(); + } + catch(E exception) + { + handler(exception, this.range); + } + } + } + } + + static if (isBidirectionalRange!IRange) + { + static if (primitivesToHandle & RangePrimitive.back) + { + @property auto ref back() + { + try + { + return this.range.back; + } + catch(E exception) + { + return handler(exception, this.range); + } + } + } + + static if (primitivesToHandle & RangePrimitive.popBack) + { + void popBack() + { + try + { + this.range.popBack(); + } + catch(E exception) + { + handler(exception, this.range); + } + } + } + } + + static if (isRandomAccessRange!IRange) + { + static if (primitivesToHandle & RangePrimitive.opIndex) + { + auto ref opIndex(size_t index) + { + try + { + return this.range[index]; + } + catch(E exception) + { + return handler(exception, this.range); + } + } + } + } + + static if (hasLength!IRange) + { + static if (primitivesToHandle & RangePrimitive.length) + { + @property auto length() + { + try + { + return this.range.length; + } + catch(E exception) + { + return handler(exception, this.range); + } + } + } + } + + static if (hasSlicing!IRange) + { + static if (primitivesToHandle & RangePrimitive.opSlice) + { + static if (hasLength!IRange) + { + typeof(this) opSlice(size_t lower, size_t upper) + { + try + { + return typeof(this)(this.range[lower .. upper]); + } + catch(E exception) + { + return typeof(this)(handler(exception, this.range)); + } + } + } + else static if (is(typeof(IRange.init[size_t.init .. $]))) + { + static struct DollarToken {} + enum opDollar = DollarToken.init; + + typeof(this) opSlice(size_t lower, DollarToken) + { + try + { + return typeof(this)(this.range[lower .. $]); + } + catch(E exception) + { + return typeof(this)(handler(exception, this.range)); + } + } + + auto opSlice(size_t lower, size_t upper) + { + try + { + return takeExactly(typeof(this)(this.range[lower .. $]), upper - 1); + } + catch(E exception) + { + return takeExactly(typeof(this)(handler(exception, this.range)), 0); + } + } + } + } + } + } + + return Handler(input); +} + +/// +unittest +{ + import std.algorithm : equal, map, splitter; + import std.conv : to, ConvException; + + auto s = "12,1337z32,54,2,7,9,1z,6,8"; + + // The next line composition will throw when iterated + // as some elements of the input do not convert to integer + auto r = s.splitter(',').map!(a => to!int(a)); + + // Substitute 0 for cases of ConvException + auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0); + assert(equal(h, [12, 0, 54, 2, 7, 9, 0, 6, 8])); +} + +unittest +{ + static struct ThrowingRange + { + @property bool empty() + { + throw new Exception("empty has thrown"); + } + + @property int front() + { + throw new Exception("front has thrown"); + } + + @property int back() + { + throw new Exception("back has thrown"); + } + + void popFront() + { + throw new Exception("popFront has thrown"); + } + + void popBack() + { + throw new Exception("popBack has thrown"); + } + + int opIndex(size_t) + { + throw new Exception("opIndex has thrown"); + } + + ThrowingRange opSlice(size_t, size_t) + { + throw new Exception("opSlice has thrown"); + } + + @property size_t length() + { + throw new Exception("length has thrown"); + } + + alias opDollar = length; + + @property ThrowingRange save() + { + throw new Exception("save has thrown"); + } + } + + static assert(isInputRange!ThrowingRange); + static assert(isForwardRange!ThrowingRange); + static assert(isBidirectionalRange!ThrowingRange); + static assert(hasSlicing!ThrowingRange); + static assert(hasLength!ThrowingRange); + + auto f = ThrowingRange(); + auto fb = f.handle!(Exception, RangePrimitive.front | RangePrimitive.back, + (e, r) => -1)(); + assert(fb.front == -1); + assert(fb.back == -1); + assertThrown(fb.popFront()); + assertThrown(fb.popBack()); + assertThrown(fb.empty); + assertThrown(fb.save); + + auto pfb = f.handle!(Exception, + RangePrimitive.popFront | RangePrimitive.popBack, (e, r) => -1)(); + + pfb.popFront(); // this would throw otherwise + pfb.popBack(); // this would throw otherwise + + auto em = f.handle!(Exception, + RangePrimitive.empty, (e, r) => false)(); + + assert(!em.empty); + + auto arr = f.handle!(Exception, + RangePrimitive.opIndex, (e, r) => 1337)(); + + assert(arr[0] == 1337); + + auto save = f.handle!(Exception, + RangePrimitive.save, + function(Exception e, ref ThrowingRange r) { + return ThrowingRange(); + })(); + + save.save(); + + auto slice = f.handle!(Exception, + RangePrimitive.opSlice, (e, r) => ThrowingRange())(); + + auto sliced = slice[0 .. 1337]; // this would throw otherwise + + static struct Infinite + { + enum bool empty = false; + int front() { assert(false); } + void popFront() { assert(false); } + Infinite save() @property { assert(false); } + static struct DollarToken {} + enum opDollar = DollarToken.init; + Take!Infinite opSlice(size_t, size_t) { assert(false); } + Infinite opSlice(size_t, DollarToken) + { + throw new Exception("opSlice has thrown"); + } + } + + static assert(isInputRange!Infinite); + static assert(isInfinite!Infinite); + static assert(hasSlicing!Infinite); + + assertThrown(Infinite()[0 .. $]); + + auto infinite = Infinite.init.handle!(Exception, + RangePrimitive.opSlice, (e, r) => Infinite())(); + + auto infSlice = infinite[0 .. $]; // this would throw otherwise +} diff --git a/std/experimental/logger/core.d b/std/experimental/logger/core.d new file mode 100644 index 00000000000..e5f71cb384c --- /dev/null +++ b/std/experimental/logger/core.d @@ -0,0 +1,3021 @@ +module std.experimental.logger.core; + +import std.array; +import std.stdio; +import std.conv; +import std.datetime; +import std.string; +import std.range; +import std.traits; +import std.exception; +import std.concurrency; +import std.format; +import core.atomic; +import core.sync.mutex : Mutex; + +import std.experimental.logger.filelogger; + +shared static this() +{ + stdSharedLoggerMutex = new Mutex; +} + +/** This template evaluates if the passed $(D LogLevel) is active. +The previously described version statements are used to decide if the +$(D LogLevel) is active. The version statements only influence the compile +unit they are used with, therefore this function can only disable logging this +specific compile unit. +*/ +template isLoggingActiveAt(LogLevel ll) +{ + version (StdLoggerDisableLogging) + { + enum isLoggingActiveAt = false; + } + else + { + static if (ll == LogLevel.trace) + { + version (StdLoggerDisableTrace) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.info) + { + version (StdLoggerDisableInfo) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.warning) + { + version (StdLoggerDisableWarning) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.error) + { + version (StdLoggerDisableError) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.critical) + { + version (StdLoggerDisableCritical) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.fatal) + { + version (StdLoggerDisableFatal) enum isLoggingActiveAt = false; + } + // If `isLoggingActiveAt` didn't get defined above to false, + // we default it to true. + static if (!is(typeof(isLoggingActiveAt) == bool)) + { + enum isLoggingActiveAt = true; + } + } +} + +/// This compile-time flag is $(D true) if logging is not statically disabled. +enum isLoggingActive = isLoggingActiveAt!(LogLevel.all); + +/** This functions is used at runtime to determine if a $(D LogLevel) is +active. The same previously defined version statements are used to disable +certain levels. Again the version statements are associated with a compile +unit and can therefore not disable logging in other compile units. +pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc +*/ +bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL, + LogLevel globalLL, lazy bool condition = true) @safe +{ + switch (ll) + { + case LogLevel.trace: + version (StdLoggerDisableTrace) return false; + else break; + case LogLevel.info: + version (StdLoggerDisableInfo) return false; + else break; + case LogLevel.warning: + version (StdLoggerDisableWarning) return false; + else break; + case LogLevel.critical: + version (StdLoggerDisableCritical) return false; + else break; + case LogLevel.fatal: + version (StdLoggerDisableFatal) return false; + else break; + default: break; + } + + return ll >= globalLL + && ll >= loggerLL + && globalLL != LogLevel.off + && loggerLL != LogLevel.off + && condition; +} + +/** This template returns the $(D LogLevel) named "logLevel" of type $(D +LogLevel) defined in a user defined module where the filename has the +suffix "_loggerconfig.d". This $(D LogLevel) sets the minimal $(D LogLevel) +of the module. + +A minimal $(D LogLevel) can be defined on a per module basis. +In order to define a module $(D LogLevel) a file with a modulename +"MODULENAME_loggerconfig" must be found. If no such module exists and the +module is a nested module, it is checked if there exists a +"PARENT_MODULE_loggerconfig" module with such a symbol. +If this module exists and it contains a $(D LogLevel) called logLevel this $(D +LogLevel) will be used. This parent lookup is continued until there is no +parent module. Then the moduleLogLevel is $(D LogLevel.all). +*/ +template moduleLogLevel(string moduleName) if (!moduleName.length) +{ + // default + enum moduleLogLevel = LogLevel.all; +} + +/// +unittest +{ + static assert(moduleLogLevel!"" == LogLevel.all); +} + +/// ditto +template moduleLogLevel(string moduleName) if (moduleName.length) +{ + import std.string : format; + mixin(q{ + static if (__traits(compiles, {import %1$s : logLevel;})) + { + import %1$s : logLevel; + static assert(is(typeof(logLevel) : LogLevel), + "Expect 'logLevel' to be of Type 'LogLevel'."); + // don't enforce enum here + alias moduleLogLevel = logLevel; + } + else + // use logLevel of package or default + alias moduleLogLevel = moduleLogLevel!(parentOf(moduleName)); + }.format(moduleName ~ "_loggerconfig")); +} + +/// +unittest +{ + static assert(moduleLogLevel!"not.amodule.path" == LogLevel.all); +} + +private string parentOf(string mod) +{ + foreach_reverse (i, c; mod) + if (c == '.') return mod[0 .. i]; + return null; +} + +/* This function formates a $(D SysTime) into an $(D OutputRange). + +The $(D SysTime) is formatted similar to +$(LREF std.datatime.DateTime.toISOExtString) expect the fractional second part. +The sub second part is the upper three digest of the microsecond. +*/ +void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time) + if (isOutputRange!(OutputRange,string)) +{ + auto fsec = time.fracSec.usecs / 1000; + + formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", + time.year, time.month, time.day, time.hour, time.minute, time.second, + fsec); +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel) additionally the condition passed must be $(D true). + +Params: + ll = The $(D LogLevel) used by this log call. + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Examples: +-------------------- +log(LogLevel.warning, true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) @safe + if (args.length > 1) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, args); + } + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName, + prettyFuncName); + } + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog). + +Params: + ll = The $(D LogLevel) used by this log call. + args = The data that should be logged. + +Examples: +-------------------- +log(LogLevel.warning, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) + if (args.length > 1 && !is(Unqual!(A[0]) : bool)) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, args); + } + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName, + moduleName); + } + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the +$(D sharedLog) must be greater or equal to the $(D defaultLogLevel) +add the condition passed must be $(D true). + +Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Examples: +-------------------- +log(true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + if (args.length > 1) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, args); + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(T,moduleName)(stdThreadLocalLog.logLevel, + condition, arg, line, file, funcName, prettyFuncName); + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the +$(D sharedLog) must be greater or equal to the $(D defaultLogLevel). + +Params: + args = The data that should be logged. + +Examples: +-------------------- +log("Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if (args.length > 1 && !is(Unqual!(A[0]) : bool) + && !is(Unqual!(A[0]) == LogLevel)) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(line, file, funcName, + prettyFuncName, moduleName)(stdThreadLocalLog.logLevel, args); + } +} + +void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file, + funcName, prettyFuncName, moduleName); + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel) additionally the condition passed must be $(D true). + +Params: + ll = The $(D LogLevel) used by this log call. + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf(LogLevel.warning, true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, msg, args); + } + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel). + +Params: + ll = The $(D LogLevel) used by this log call. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf(LogLevel.warning, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy string msg, + lazy A args) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, msg, args); + } + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D defaultLogLevel) additionally the condition +passed must be $(D true). + +Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf(true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, msg, args); + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D defaultLogLevel). + +Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf("Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, msg, args); + } +} + +/** This template provides the global log functions with the $(D LogLevel) +is encoded in the function name. + +The aliases following this template create the public names of these log +functions. +*/ +template defaultLogFunction(LogLevel ll) +{ + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) @safe + if (args.length > 0 && !is(Unqual!(A[0]) : bool)) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(args); + } + } + + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(condition, args); + } + } +} + +/** This function logs data to the $(D stdThreadLocalLog). + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D stdThreadLocalLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + args = The data that should be logged. + +Examples: +-------------------- +trace(1337, "is number"); +info(1337, "is number"); +error(1337, "is number"); +critical(1337, "is number"); +fatal(1337, "is number"); +-------------------- + +The second version of the function logs data to the $(D stdThreadLocalLog) depending +on a condition. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D stdThreadLocalLog) and +must be greater or equal than the global $(D LogLevel) additionally the +condition passed must be $(D true). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Examples: +-------------------- +trace(true, 1337, "is number"); +info(false, 1337, "is number"); +error(true, 1337, "is number"); +critical(false, 1337, "is number"); +fatal(true, 1337, "is number"); +-------------------- +*/ +alias trace = defaultLogFunction!(LogLevel.trace); +/// Ditto +alias info = defaultLogFunction!(LogLevel.info); +/// Ditto +alias warning = defaultLogFunction!(LogLevel.warning); +/// Ditto +alias error = defaultLogFunction!(LogLevel.error); +/// Ditto +alias critical = defaultLogFunction!(LogLevel.critical); +/// Ditto +alias fatal = defaultLogFunction!(LogLevel.fatal); + +/** This template provides the global $(D printf)-style log functions with +the $(D LogLevel) is encoded in the function name. + +The aliases following this template create the public names of the log +functions. +*/ +template defaultLogFunctionf(LogLevel ll) +{ + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(msg, args); + } + } + + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(condition, msg, args); + } + } +} + +/** This function logs data to the $(D sharedLog) in a $(D printf)-style +manner. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D sharedLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +tracef("is number %d", 1); +infof("is number %d", 2); +errorf("is number %d", 3); +criticalf("is number %d", 4); +fatalf("is number %d", 5); +-------------------- + +The second version of the function logs data to the $(D sharedLog) in a $(D +printf)-style manner. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D sharedLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +tracef(false, "is number %d", 1); +infof(false, "is number %d", 2); +errorf(true, "is number %d", 3); +criticalf(true, "is number %d", 4); +fatalf(someFunct(), "is number %d", 5); +-------------------- +*/ +alias tracef = defaultLogFunctionf!(LogLevel.trace); +/// Ditto +alias infof = defaultLogFunctionf!(LogLevel.info); +/// Ditto +alias warningf = defaultLogFunctionf!(LogLevel.warning); +/// Ditto +alias errorf = defaultLogFunctionf!(LogLevel.error); +/// Ditto +alias criticalf = defaultLogFunctionf!(LogLevel.critical); +/// Ditto +alias fatalf = defaultLogFunctionf!(LogLevel.fatal); + +private struct MsgRange +{ + import std.traits : isSomeString, isSomeChar; + + private Logger log; + + private char[] buffer; + + this(Logger log) @safe + { + this.log = log; + } + + void put(T)(T msg) @safe + if (isSomeString!T) + { + log.logMsgPart(msg); + } + + void put(dchar elem) @safe + { + import std.utf : encode; + encode(this.buffer, elem); + log.logMsgPart(this.buffer); + } +} + +private void formatString(A...)(MsgRange oRange, A args) +{ + import std.format : formattedWrite; + + foreach (arg; args) + { + std.format.formattedWrite(oRange, "%s", arg); + } +} + +unittest +{ + void dummy() @safe + { + auto tl = new TestLogger(); + auto dst = MsgRange(tl); + formatString(dst, "aaa", "bbb"); + } + + dummy(); +} + +/** +There are eight usable logging level. These level are $(I all), $(I trace), +$(I info), $(I warning), $(I error), $(I critical), $(I fatal), and $(I off). +If a log function with $(D LogLevel.fatal) is called the shutdown handler of +that logger is called. +*/ +enum LogLevel : ubyte +{ + all = 1, /** Lowest possible assignable $(D LogLevel). */ + trace = 32, /** $(D LogLevel) for tracing the execution of the program. */ + info = 64, /** This level is used to display information about the + program. */ + warning = 96, /** warnings about the program should be displayed with this + level. */ + error = 128, /** Information about errors should be logged with this + level.*/ + critical = 160, /** Messages that inform about critical errors should be + logged with this level. */ + fatal = 192, /** Log messages that describe fatal errors should use this + level. */ + off = ubyte.max /** Highest possible $(D LogLevel). */ +} + +/** This class is the base of every logger. In order to create a new kind of +logger a deriving class needs to implement the $(D writeLogMsg) method. By +default this is not thread-safe. + +It is also possible to $(D override) the three methods $(D beginLogMsg), +$(D logMsgPart) and $(D finishLogMsg) together, this option gives more +flexibility. +*/ +abstract class Logger +{ + /** LogEntry is a aggregation combining all information associated + with a log message. This aggregation will be passed to the method + writeLogMsg. + */ + protected struct LogEntry + { + /// the filename the log function was called from + string file; + /// the line number the log function was called from + int line; + /// the name of the function the log function was called from + string funcName; + /// the pretty formatted name of the function the log function was + /// called from + string prettyFuncName; + /// the name of the module the log message is coming from + string moduleName; + /// the $(D LogLevel) associated with the log message + LogLevel logLevel; + /// thread id of the log message + Tid threadId; + /// the time the message was logged + SysTime timestamp; + /// the message of the log message + string msg; + /// A refernce to the $(D Logger) used to create this $(D LogEntry) + Logger logger; + } + + /** This constructor takes a name of type $(D string), and a $(D LogLevel). + + Every subclass of $(D Logger) has to call this constructor from there + constructor. It sets the $(D LogLevel), the name of the $(D Logger), and + creates a fatal handler. The fatal handler will throw an $(D Error) if a + log call is made with a $(D LogLevel) $(D LogLevel.fatal). + */ + this(LogLevel lv) @safe + { + this.logLevel_ = lv; + this.fatalHandler_ = delegate() { + throw new Error("A fatal log message was logged"); + }; + // TODO: remove lambda hack after relevant druntime PR gets merged + this.mutex = () @trusted { return new Mutex(); } (); + } + + /** A custom logger must implement this method in order to work in a + $(D MultiLogger) and $(D ArrayLogger). + + Params: + payload = All information associated with call to log function. + + See_Also: beginLogMsg, logMsgPart, finishLogMsg + */ + abstract protected void writeLogMsg(ref LogEntry payload) @safe; + + /* The default implementation will use an $(D std.array.appender) + internally to construct the message string. This means dynamic, + GC memory allocation. A logger can avoid this allocation by + reimplementing $(D beginLogMsg), $(D logMsgPart) and $(D finishLogMsg). + $(D beginLogMsg) is always called first, followed by any number of calls + to $(D logMsgPart) and one call to $(D finishLogMsg). + + As an example for such a custom $(D Logger) compare this: + ---------------- + class CLogger : Logger + { + override void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp) + { + ... logic here + } + + override void logMsgPart(const(char)[] msg) + { + ... logic here + } + + override void finishLogMsg() + { + ... logic here + } + + void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + } + ---------------- + */ + protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + static if (isLoggingActive) + { + msgAppender = appender!string(); + header = LogEntry(file, line, funcName, prettyFuncName, + moduleName, logLevel, threadId, timestamp, null, logger); + } + } + + /** Logs a part of the log message. */ + protected void logMsgPart(const(char)[] msg) @safe + { + static if (isLoggingActive) + { + msgAppender.put(msg); + } + } + + /** Signals that the message has been written and no more calls to + $(D logMsgPart) follow. */ + protected void finishLogMsg() @safe + { + static if (isLoggingActive) + { + header.msg = msgAppender.data; + this.writeLogMsg(header); + } + } + + /** The $(D LogLevel) determines if the log call are processed or dropped + by the $(D Logger). In order for the log call to be processed the + $(D LogLevel) of the log call must be greater or equal to the $(D LogLevel) + of the $(D logger). + + These two methods set and get the $(D LogLevel) of the used $(D Logger). + + Example: + ----------- + auto f = new FileLogger(stdout); + f.logLevel = LogLevel.info; + assert(f.logLevel == LogLevel.info); + ----------- + */ + @property final LogLevel logLevel() const pure @safe @nogc + { + return trustedLoad(this.logLevel_); + } + + /// Ditto + @property final void logLevel(const LogLevel lv) pure @safe @nogc + { + synchronized (mutex) this.logLevel_ = lv; + } + + /** This $(D delegate) is called in case a log message with + $(D LogLevel.fatal) gets logged. + + By default an $(D Error) will be thrown. + */ + @property final void delegate() fatalHandler() const pure @safe @nogc + { + synchronized (mutex) return this.fatalHandler_; + } + + /// Ditto + @property final void fatalHandler(void delegate() @safe fh) pure @safe @nogc + { + synchronized (mutex) this.fatalHandler_ = fh; + } + + /** This method allows forwarding log entries from one logger to another. + + $(D forwardMsg) will ensure proper synchronization and then call + $(D writeLogMsg). This is an API for implementing your own loggers and + should not be called by normal user code. A notable difference from other + logging functions is that the $(D globalLogLevel) wont be evaluated again + since it is assumed that the caller already checked that. + */ + void forwardMsg(ref LogEntry payload) @trusted + { + //writeln(payload); + static if (isLoggingActive) synchronized (mutex) + { + //writeln(__LINE__, " ",payload, this.logLevel_, " ", globalLogLevel); + //writeln(isLoggingEnabled(payload.logLevel, this.logLevel_, + // globalLogLevel), payload.logLevel >= this.logLevel_, + // payload.logLevel >= globalLogLevel); + if (isLoggingEnabled(payload.logLevel, this.logLevel_, + globalLogLevel)) + { + //writeln(__LINE__, " ",payload); + this.writeLogMsg(payload); + + if (payload.logLevel == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This template provides the log functions for the $(D Logger) $(D class) + with the $(D LogLevel) encoded in the function name. + + For further information see the the two functions defined inside of this + template. + + The aliases following this template create the public names of these log + functions. + */ + template memLogFunctions(LogLevel ll) + { + /** This function logs data to the used $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.trace(1337, "is number"); + s.info(1337, "is number"); + s.error(1337, "is number"); + s.critical(1337, "is number"); + s.fatal(1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if (args.length == 0 || (args.length > 0 && !is(A[0] : bool))) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + condition. + + In order for the resulting log message to be logged the $(D LogLevel) must + be greater or equal than the $(D LogLevel) of the used $(D Logger) and + must be greater or equal than the global $(D LogLevel) additionally the + condition passed must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.trace(true, 1337, "is number"); + s.info(false, 1337, "is number"); + s.error(true, 1337, "is number"); + s.critical(false, 1337, "is number"); + s.fatal(true, 1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy A args) @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel) additionally + the passed condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stderr); + s.tracef(true, "is number %d", 1); + s.infof(true, "is number %d", 2); + s.errorf(false, "is number %d", 3); + s.criticalf(someFunc(), "is number %d", 4); + s.fatalf(true, "is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) must + be greater or equal than the $(D LogLevel) of the used $(D Logger) and + must be greater or equal than the global $(D LogLevel). + + Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stderr); + s.tracef("is number %d", 1); + s.infof("is number %d", 2); + s.errorf("is number %d", 3); + s.criticalf("is number %d", 4); + s.fatalf("is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + } + + /// Ditto + alias trace = memLogFunctions!(LogLevel.trace).logImpl; + /// Ditto + alias tracef = memLogFunctions!(LogLevel.trace).logImplf; + /// Ditto + alias info = memLogFunctions!(LogLevel.info).logImpl; + /// Ditto + alias infof = memLogFunctions!(LogLevel.info).logImplf; + /// Ditto + alias warning = memLogFunctions!(LogLevel.warning).logImpl; + /// Ditto + alias warningf = memLogFunctions!(LogLevel.warning).logImplf; + /// Ditto + alias error = memLogFunctions!(LogLevel.error).logImpl; + /// Ditto + alias errorf = memLogFunctions!(LogLevel.error).logImplf; + /// Ditto + alias critical = memLogFunctions!(LogLevel.critical).logImpl; + /// Ditto + alias criticalf = memLogFunctions!(LogLevel.critical).logImplf; + /// Ditto + alias fatal = memLogFunctions!(LogLevel.fatal).logImpl; + /// Ditto + alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf; + + /** This method logs data with the $(D LogLevel) of the used $(D Logger). + + This method takes a $(D bool) as first argument. In order for the + data to be processed the $(D bool) must be $(D true) and the $(D LogLevel) + of the Logger must be greater or equal to the global $(D LogLevel). + + Params: + args = The data that should be logged. + condition = The condition must be $(D true) for the data to be logged. + args = The data that is to be logged. + + Returns: The logger used by the logging function as reference. + + Examples: + -------------------- + auto l = new StdioLogger(); + l.log(1337); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) @safe + if (args.length > 1) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition) && ll >= moduleLogLevel!moduleName) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel). + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.log(LogLevel.trace, 1337, "is number"); + s.log(LogLevel.info, 1337, "is number"); + s.log(LogLevel.warning, 1337, "is number"); + s.log(LogLevel.error, 1337, "is number"); + s.log(LogLevel.fatal, 1337, "is number"); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) + @safe + if (args.length > 1 && !is(Unqual!(A[0]) : bool)) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T)(const LogLevel ll, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + explicitly passed condition with the $(D LogLevel) of the used + $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel) and the condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(false, 1337, "is number"); + s.log(false, 1337, "is number"); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + @safe + if (args.length > 1) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T)(lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with the $(D LogLevel) + of the used $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel). + + Params: + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.log(1337, "is number"); + s.log(info, 1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + @safe + if (args.length > 1 + && !is(Unqual!(A[0]) : bool) + && !is(Unqual!(A[0]) == LogLevel)) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, arg); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel) and depending on a condition in a $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel) and the + condition must be $(D true). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + condition = The condition must be $(D true) for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.info, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.warning, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.error, false ,"%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, true ,"%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel) in a $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, "%d %s", 1337, "is number"); + s.logf(LogLevel.info, "%d %s", 1337, "is number"); + s.logf(LogLevel.warning, "%d %s", 1337, "is number"); + s.logf(LogLevel.error, "%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, "%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + condition with the $(D LogLevel) of the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel) and the condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(false ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + //writeln(msg, args, " ", line); + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + //writeln(msg, args, " ", line); + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This method logs data to the used $(D Logger) with the $(D LogLevel) + of the this $(D Logger) in a $(D printf)-style manner. + + In order for the data to be processed the $(D LogLevel) of the $(D Logger) + must be greater or equal to the global $(D LogLevel). + + Params: + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + private void delegate() @safe fatalHandler_; + private shared LogLevel logLevel_ = LogLevel.info; + private Mutex mutex; + + protected Appender!string msgAppender; + protected LogEntry header; +} + +// Thread Global + +private __gshared Mutex stdSharedLoggerMutex; +private __gshared Logger stdSharedDefaultLogger; +private shared Logger stdSharedLogger; +private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all; + +/* This method returns the global default Logger. + * Marked @trusted because of excessive reliance on __gshared data + */ +private @property Logger defaultSharedLoggerImpl() @trusted +{ + static __gshared ubyte[__traits(classInstanceSize, FileLogger)] _buffer; + + synchronized (stdSharedLoggerMutex) + { + auto buffer = cast(ubyte[]) _buffer; + + if (stdSharedDefaultLogger is null) + { + stdSharedDefaultLogger = emplace!FileLogger(buffer, stderr, LogLevel.all); + } + } + return stdSharedDefaultLogger; +} + +/** This property sets and gets the default $(D Logger). + +Example: +------------- +sharedLog = new FileLogger(yourFile); +------------- +The example sets a new $(D FileLogger) as new $(D sharedLog). + +If at some point you want to use the original default logger again, you can +use $(D sharedLog = null;). This will put back the original. + +Note: +While getting and setting $(D sharedLog) is thread-safe, it has to be considered +that the returned reference is only a current snapshot and in the following +code, you must make sure no other thread reassigns to it between reading and +writing $(D sharedLog). +------------- +if (sharedLog !is myLogger) + sharedLog = new myLogger; +------------- +*/ +@property Logger sharedLog() @safe +{ + static auto trustedLoad(ref shared Logger logger) @trusted + { + return atomicLoad!(MemoryOrder.acq)(logger); + } + + // If we have set up our own logger use that + if (auto logger = trustedLoad(stdSharedLogger)) + { + return logger; + } + else + { + // Otherwise resort to the default logger + return defaultSharedLoggerImpl; + } +} + +/// Ditto +@property void sharedLog(Logger logger) @trusted +{ + atomicStore!(MemoryOrder.rel)(stdSharedLogger, cast(shared) logger); +} + +/** This methods get and set the global $(D LogLevel). + +Every log message with a $(D LogLevel) lower as the global $(D LogLevel) +will be discarded before it reaches $(D writeLogMessage) method of any +$(D Logger). +*/ +/* Implementation note: +For any public logging call, the global log level shall only be queried once on +entry. Otherwise when another threads changes the level, we wouls work with +different levels at different spots in the code. +*/ +@property LogLevel globalLogLevel() @safe @nogc +{ + return trustedLoad(stdLoggerGlobalLogLevel); +} + +/// Ditto +@property void globalLogLevel(LogLevel ll) @safe +{ + trustedStore(stdLoggerGlobalLogLevel, ll); +} + +// Thread Local + +/** The $(D StdForwardLogger) will always forward anything to the sharedLog. + +The $(D StdForwardLogger) will not throw if data is logged with $(D +LogLevel.fatal). +*/ +class StdForwardLogger : Logger +{ + /** The default constructor for the $(D StdForwardLogger). + + Params: + lv = The $(D LogLevel) for the $(D MultiLogger). By default the $(D + LogLevel) is $(D all). + */ + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + this.fatalHandler = delegate() {}; + } + + override protected void writeLogMsg(ref LogEntry payload) + { + sharedLog.forwardMsg(payload); + } +} + +/// +@safe unittest +{ + auto nl1 = new StdForwardLogger(LogLevel.all); +} + +/** This $(D LogLevel) is unqiue to every thread. + +The thread local $(D Logger) will use this $(D LogLevel) to filter log calls +every same way as presented earlier. +*/ +//public LogLevel threadLogLevel = LogLevel.all; +private Logger stdLoggerThreadLogger; +private Logger stdLoggerDefaultThreadLogger; + +/* This method returns the thread local default Logger. +*/ +private @property Logger stdThreadLocalLogImpl() @trusted +{ + static ubyte[__traits(classInstanceSize, StdForwardLogger)] _buffer; + + auto buffer = cast(ubyte[]) _buffer; + + if (stdLoggerDefaultThreadLogger is null) + { + stdLoggerDefaultThreadLogger = emplace!StdForwardLogger(buffer, LogLevel.all); + } + return stdLoggerDefaultThreadLogger; +} + +/** This function returns a thread unique $(D Logger), that by default +propergates all data logged to it to the $(D sharedLog). + +These properties can be used to set and get this $(D Logger). Every +modification to this $(D Logger) will only be visible in the thread the +modification has been done from. + +This $(D Logger) is called by the free standing log functions. This allows to +create thread local redirections and still use the free standing log +functions. +*/ +@property Logger stdThreadLocalLog() @safe +{ + // If we have set up our own logger use that + if (auto logger = stdLoggerThreadLogger) + return logger; + else + // Otherwise resort to the default logger + return stdThreadLocalLogImpl; +} + +/// Ditto +@property void stdThreadLocalLog(Logger logger) @safe +{ + stdLoggerThreadLogger = logger; +} + +/// Ditto +unittest +{ + Logger l = stdThreadLocalLog; + stdThreadLocalLog = new FileLogger("someFile.log"); + + stdThreadLocalLog = l; +} + +version (unittest) +{ + import std.array; + import std.ascii; + import std.random; + + @trusted package string randomString(size_t upto) + { + auto app = Appender!string(); + foreach (_ ; 0 .. upto) + app.put(letters[uniform(0, letters.length)]); + return app.data; + } +} + +@safe unittest +{ + LogLevel ll = globalLogLevel; + globalLogLevel = LogLevel.fatal; + assert(globalLogLevel == LogLevel.fatal); + globalLogLevel = ll; +} + +version (unittest) +{ + package class TestLogger : Logger + { + int line = -1; + string file = null; + string func = null; + string prettyFunc = null; + string msg = null; + LogLevel lvl; + + this(const LogLevel lv = LogLevel.info) @safe + { + super(lv); + } + + override protected void writeLogMsg(ref LogEntry payload) @safe + { + this.line = payload.line; + this.file = payload.file; + this.func = payload.funcName; + this.prettyFunc = payload.prettyFuncName; + this.lvl = payload.logLevel; + this.msg = payload.msg; + } + } + + private void testFuncNames(Logger logger) @safe + { + string s = "I'm here"; + logger.log(s); + } +} + +@safe unittest +{ + auto tl1 = new TestLogger(); + testFuncNames(tl1); + assert(tl1.func == "std.experimental.logger.core.testFuncNames", tl1.func); + assert(tl1.prettyFunc == + "void std.experimental.logger.core.testFuncNames(Logger logger) @safe", + tl1.prettyFunc); + assert(tl1.msg == "I'm here", tl1.msg); +} + +@safe unittest +{ + import std.experimental.logger.multilogger : MultiLogger; + + auto tl1 = new TestLogger; + auto tl2 = new TestLogger; + + auto ml = new MultiLogger(); + ml.insertLogger("one", tl1); + ml.insertLogger("two", tl2); + + string msg = "Hello Logger World"; + ml.log(msg); + int lineNumber = __LINE__ - 1; + assert(tl1.msg == msg); + assert(tl1.line == lineNumber); + assert(tl2.msg == msg); + assert(tl2.line == lineNumber); + + ml.removeLogger("one"); + ml.removeLogger("two"); + auto n = ml.removeLogger("one"); + assert(n is null); +} + +@safe unittest +{ + bool errorThrown = false; + auto tl = new TestLogger; + auto dele = delegate() { + errorThrown = true; + }; + tl.fatalHandler = dele; + tl.fatal(); + assert(errorThrown); +} + +@safe unittest +{ + auto l = new TestLogger(LogLevel.all); + string msg = "Hello Logger World"; + l.log(msg); + int lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.log(true, msg); + lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.log(false, msg); + assert(l.msg == msg); + assert(l.line == lineNumber, to!string(l.line)); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + l.logf(msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.logf(true, msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.logf(false, msg, "Yet"); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(l.logf(LogLevel.fatal, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(l.logf(LogLevel.fatal, true, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + assertNotThrown(l.logf(LogLevel.fatal, false, msg, "Yet")); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + auto oldunspecificLogger = sharedLog; + + assert(oldunspecificLogger.logLevel == LogLevel.all, + to!string(oldunspecificLogger.logLevel)); + + assert(l.logLevel == LogLevel.all); + sharedLog = l; + assert(globalLogLevel == LogLevel.all, + to!string(globalLogLevel)); + + scope(exit) + { + sharedLog = oldunspecificLogger; + } + + assert(sharedLog.logLevel == LogLevel.all); + assert(stdThreadLocalLog.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + msg = "Another message"; + log(msg); + lineNumber = __LINE__ - 1; + assert(l.logLevel == LogLevel.all); + assert(l.line == lineNumber, to!string(l.line)); + assert(l.msg == msg, l.msg); + + log(true, msg); + lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + log(false, msg); + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + logf(msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + logf(true, msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + logf(false, msg, "Yet"); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + () @trusted { + assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(logf(LogLevel.fatal, true, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + assertNotThrown(logf(LogLevel.fatal, false, msg, "Yet")); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); +} + +unittest // default logger +{ + import std.file : remove; + string filename = randomString(32) ~ ".tempLogFile"; + FileLogger l = new FileLogger(filename); + auto oldunspecificLogger = sharedLog; + sharedLog = l; + + scope(exit) + { + remove(filename); + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + globalLogLevel = LogLevel.critical; + assert(globalLogLevel == LogLevel.critical); + + log(LogLevel.warning, notWritten); + log(LogLevel.critical, written); + + l.file.flush(); + l.file.close(); + + auto file = File(filename, "r"); + assert(!file.eof); + + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + assert(readLine.indexOf(notWritten) == -1, readLine); + file.close(); +} + +unittest +{ + import std.file : remove; + import core.memory : destroy; + string filename = randomString(32) ~ ".tempLogFile"; + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + remove(filename); + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + auto l = new FileLogger(filename); + sharedLog = l; + sharedLog.logLevel = LogLevel.critical; + + log(LogLevel.error, false, notWritten); + log(LogLevel.critical, true, written); + destroy(l); + + auto file = File(filename, "r"); + auto readLine = file.readln(); + assert(!readLine.empty, readLine); + assert(readLine.indexOf(written) != -1); + assert(readLine.indexOf(notWritten) == -1); + file.close(); +} + +@safe unittest +{ + auto tl = new TestLogger(LogLevel.all); + int l = __LINE__; + tl.info("a"); + assert(tl.line == l+1); + assert(tl.msg == "a"); + assert(tl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + l = __LINE__; + tl.trace("b"); + assert(tl.msg == "b", tl.msg); + assert(tl.line == l+1, to!string(tl.line)); +} + +// testing possible log conditions +@safe unittest +{ + auto oldunspecificLogger = sharedLog; + + auto mem = new TestLogger; + mem.fatalHandler = delegate() {}; + sharedLog = mem; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + int value = 0; + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + mem.logLevel = ll; + + foreach (cond; [true, false]) + { + foreach (condValue; [true, false]) + { + foreach (memOrG; [true, false]) + { + foreach (prntf; [true, false]) + { + foreach (ll2; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, + LogLevel.error, LogLevel.critical, + LogLevel.fatal, LogLevel.off]) + { + foreach (singleMulti; 0 .. 2) + { + int lineCall; + mem.msg = "-1"; + if (memOrG) + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + mem.logf(ll2, condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + mem.logf(ll2, condValue, + "%d %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.logf(ll2, "%s", value); + lineCall = __LINE__; + } + else + { + mem.logf(ll2, "%d %d", + value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + mem.log(ll2, condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(ll2, condValue, + to!string(value), value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.log(ll2, to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(ll2, + to!string(value), + value); + lineCall = __LINE__; + } + } + } + } + else + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + logf(ll2, condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + logf(ll2, condValue, + "%s %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + logf(ll2, "%s", value); + lineCall = __LINE__; + } + else + { + logf(ll2, "%s %s", value, + value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + log(ll2, condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + log(ll2, condValue, value, + to!string(value)); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + log(ll2, to!string(value)); + lineCall = __LINE__; + } + else + { + log(ll2, value, + to!string(value)); + lineCall = __LINE__; + } + } + } + } + + string valueStr = to!string(value); + ++value; + + bool gllOff = (gll != LogLevel.off); + bool llOff = (ll != LogLevel.off); + bool condFalse = (cond ? condValue : true); + bool ll2VSgll = (ll2 >= gll); + bool ll2VSll = (ll2 >= ll); + + bool shouldLog = gllOff && llOff && condFalse + && ll2VSgll && ll2VSll; + + /* + writefln( + "go(%b) ll2o(%b) c(%b) lg(%b) ll(%b) s(%b)" + , gll != LogLevel.off, ll2 != LogLevel.off, + cond ? condValue : true, + ll2 >= gll, ll2 >= ll, shouldLog); + */ + + + if (shouldLog) + { + assert(mem.msg.indexOf(valueStr) != -1, + format( + "lineCall(%d) gll(%u) ll(%u) ll2(%u) " ~ + "cond(%b) condValue(%b)" ~ + " memOrG(%b) shouldLog(%b) %s == %s" ~ + " %b %b %b %b %b", + lineCall, gll, ll, ll2, cond, + condValue, memOrG, shouldLog, mem.msg, + valueStr, gllOff, llOff, condFalse, + ll2VSgll, ll2VSll + )); + } + else + { + assert(mem.msg.indexOf(valueStr), + format( + "lineCall(%d) gll(%u) ll(%u) ll2(%u) " ~ + " cond(%b)condValue(%b) memOrG(%b) " ~ + "shouldLog(%b) %s != %s", gll, + lineCall, ll, ll2, cond, condValue, + memOrG, shouldLog, mem.msg, valueStr + )); + } + } + } + } + } + } + } + } + } +} + +// more testing +@safe unittest +{ + auto oldunspecificLogger = sharedLog; + + auto mem = new TestLogger; + mem.fatalHandler = delegate() {}; + sharedLog = mem; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + int value = 0; + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + mem.logLevel = ll; + + foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + stdThreadLocalLog.logLevel = tll; + + foreach (cond; [true, false]) + { + foreach (condValue; [true, false]) + { + foreach (memOrG; [true, false]) + { + foreach (prntf; [true, false]) + { + foreach (singleMulti; 0 .. 2) + { + int lineCall; + mem.msg = "-1"; + if (memOrG) + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + mem.logf(condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + mem.logf(condValue, + "%d %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.logf("%s", value); + lineCall = __LINE__; + } + else + { + mem.logf("%d %d", + value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + mem.log(condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(condValue, + to!string(value), value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.log(to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(to!string(value), + value); + lineCall = __LINE__; + } + } + } + } + else + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + logf(condValue, "%s", value); + lineCall = __LINE__; + } + else + { + logf(condValue, "%s %d", value, + value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + logf("%s", value); + lineCall = __LINE__; + } + else + { + logf("%s %s", value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + log(condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + log(condValue, value, + to!string(value)); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + log(to!string(value)); + lineCall = __LINE__; + } + else + { + log(value, to!string(value)); + lineCall = __LINE__; + } + } + } + } + + string valueStr = to!string(value); + ++value; + + bool gllOff = (gll != LogLevel.off); + bool llOff = (ll != LogLevel.off); + bool tllOff = (tll != LogLevel.off); + bool llVSgll = (ll >= gll); + bool tllVSll = + (stdThreadLocalLog.logLevel >= ll); + bool condFalse = (cond ? condValue : true); + + bool shouldLog = gllOff && llOff + && (memOrG ? true : tllOff) + && (memOrG ? + (ll >= gll) : + (tll >= gll && tll >= ll)) + && condFalse; + + if (shouldLog) + { + assert(mem.msg.indexOf(valueStr) != -1, + format("\ngll(%s) ll(%s) tll(%s) " ~ + "cond(%s) condValue(%s) " ~ + "memOrG(%s) prntf(%s) " ~ + "singleMulti(%s)", + gll, ll, tll, cond, condValue, + memOrG, prntf, singleMulti) + ~ format(" gllOff(%s) llOff(%s) " ~ + "llVSgll(%s) tllVSll(%s) " ~ + "tllOff(%s) condFalse(%s) " + ~ "shoudlLog(%s)", + gll != LogLevel.off, + ll != LogLevel.off, llVSgll, + tllVSll, tllOff, condFalse, + shouldLog) + ~ format("msg(%s) line(%s) " ~ + "lineCall(%s) valueStr(%s)", + mem.msg, mem.line, lineCall, + valueStr) + ); + } + else + { + assert(mem.msg.indexOf(valueStr) == -1, + format("\ngll(%s) ll(%s) tll(%s) " ~ + "cond(%s) condValue(%s) " ~ + "memOrG(%s) prntf(%s) " ~ + "singleMulti(%s)", + gll, ll, tll, cond, condValue, + memOrG, prntf, singleMulti) + ~ format(" gllOff(%s) llOff(%s) " ~ + "llVSgll(%s) tllVSll(%s) " ~ + "tllOff(%s) condFalse(%s) " + ~ "shoudlLog(%s)", + gll != LogLevel.off, + ll != LogLevel.off, llVSgll, + tllVSll, tllOff, condFalse, + shouldLog) + ~ format("msg(%s) line(%s) " ~ + "lineCall(%s) valueStr(%s)", + mem.msg, mem.line, lineCall, + valueStr) + ); + } + } + } + } + } + } + } + } + } +} + +// testing more possible log conditions +@safe unittest +{ + bool fatalLog; + auto mem = new TestLogger; + mem.fatalHandler = delegate() { fatalLog = true; }; + auto oldunspecificLogger = sharedLog; + + stdThreadLocalLog.logLevel = LogLevel.all; + + sharedLog = mem; + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + mem.logLevel = ll; + + foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + stdThreadLocalLog.logLevel = tll; + + foreach (cond; [true, false]) + { + assert(globalLogLevel == gll); + assert(mem.logLevel == ll); + + bool gllVSll = LogLevel.trace >= globalLogLevel; + bool llVSgll = ll >= globalLogLevel; + bool lVSll = LogLevel.trace >= ll; + bool gllOff = globalLogLevel != LogLevel.off; + bool llOff = mem.logLevel != LogLevel.off; + bool tllOff = stdThreadLocalLog.logLevel != LogLevel.off; + bool tllVSll = tll >= ll; + bool tllVSgll = tll >= gll; + bool lVSgll = LogLevel.trace >= tll; + + bool test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + bool testG = gllOff && llOff && tllOff && lVSgll && tllVSll && tllVSgll && cond; + + mem.line = -1; + /* + writefln("gll(%3u) ll(%3u) cond(%b) test(%b)", + gll, ll, cond, test); + writefln("%b %b %b %b %b %b test2(%b)", llVSgll, gllVSll, lVSll, + gllOff, llOff, cond, test2); + */ + + mem.trace(__LINE__); int line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + trace(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.trace(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + trace(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.tracef("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + tracef("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.tracef(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + tracef(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.info >= ll; + lVSgll = LogLevel.info >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.info(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + info(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.info(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + info(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.infof("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + infof("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.infof(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + infof(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.warning >= ll; + lVSgll = LogLevel.warning >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.warning(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warning(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warning(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warning(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warningf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warningf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warningf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warningf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.critical >= ll; + lVSgll = LogLevel.critical >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.critical(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + critical(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.critical(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + critical(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.criticalf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + criticalf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.criticalf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + criticalf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.fatal >= ll; + lVSgll = LogLevel.fatal >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.fatal(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatal(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatal(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatal(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatalf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatalf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatalf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatalf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + } + } + } + } +} + +// Issue #5 +@safe unittest +{ + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + auto tl = new TestLogger(LogLevel.info); + sharedLog = tl; + + trace("trace"); + assert(tl.msg.indexOf("trace") == -1); +} + +// Issue #5 +@safe unittest +{ + import std.experimental.logger.multilogger : MultiLogger; + stdThreadLocalLog.logLevel = LogLevel.all; + + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + auto logger = new MultiLogger(LogLevel.error); + + auto tl = new TestLogger(LogLevel.info); + logger.insertLogger("required", tl); + sharedLog = logger; + + trace("trace"); + assert(tl.msg.indexOf("trace") == -1); + info("info"); + assert(tl.msg.indexOf("info") == -1); + error("error"); + assert(tl.msg.indexOf("error") == 0); +} + +unittest +{ + import std.exception : assertThrown; + auto tl = new TestLogger(); + assertThrown!Throwable(tl.fatal("fatal")); +} + +// log objects with non-safe toString +unittest +{ + struct Test + { + string toString() const @system + { + return "test"; + } + } + + auto tl = new TestLogger(); + tl.info(Test.init); + assert(tl.msg == "test"); +} + +// Workaround for atomics not allowed in @safe code +private auto trustedLoad(T)(ref shared T value) @trusted +{ + return atomicLoad!(MemoryOrder.acq)(value); +} + +// ditto +private void trustedStore(T)(ref shared T dst, ref T src) @trusted +{ + atomicStore!(MemoryOrder.rel)(dst, src); +} + +// check that thread-local logging does not propagate +// to shared logger +unittest +{ + import std.concurrency, core.atomic, core.thread; + + static shared logged_count = 0; + + class TestLog : Logger + { + Tid tid; + + this() + { + super (LogLevel.trace); + this.tid = thisTid; + } + + override void writeLogMsg(ref LogEntry payload) @trusted + { + assert(thisTid == this.tid); + atomicOp!"+="(logged_count, 1); + } + } + + class IgnoredLog : Logger + { + this() + { + super (LogLevel.trace); + } + + override void writeLogMsg(ref LogEntry payload) @trusted + { + assert(false); + } + } + + auto oldSharedLog = sharedLog; + scope(exit) + { + sharedLog = oldSharedLog; + } + + sharedLog = new IgnoredLog; + Thread[] spawned; + + foreach (i; 0..4) + { + spawned ~= new Thread({ + stdThreadLocalLog = new TestLog; + trace("zzzzzzzzzz"); + }); + spawned[$-1].start(); + } + + foreach (t; spawned) + t.join(); + + assert (atomicOp!"=="(logged_count, 4)); +} + +@safe unittest +{ + auto dl = cast(FileLogger)sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger)stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/std/experimental/logger/filelogger.d b/std/experimental/logger/filelogger.d new file mode 100644 index 00000000000..270e962f475 --- /dev/null +++ b/std/experimental/logger/filelogger.d @@ -0,0 +1,202 @@ +module std.experimental.logger.filelogger; + +import std.stdio; +import std.experimental.logger.core; + +/** This $(D Logger) implementation writes log messages to the associated +file. The name of the file has to be passed on construction time. If the file +is already present new log messages will be append at its end. +*/ +class FileLogger : Logger +{ + import std.format : formattedWrite; + import std.datetime : SysTime; + import std.concurrency : Tid; + + /** A constructor for the $(D FileLogger) Logger. + + Params: + fn = The filename of the output file of the $(D FileLogger). If that + file can not be opened for writting an exception will be thrown. + lv = The $(D LogLevel) for the $(D FileLogger). By default the + $(D LogLevel) for $(D FileLogger) is $(D LogLevel.info). + + Example: + ------------- + auto l1 = new FileLogger("logFile", "loggerName"); + auto l2 = new FileLogger("logFile", "loggerName", LogLevel.fatal); + ------------- + */ + this(in string fn, const LogLevel lv = LogLevel.info) @safe + { + import std.exception : enforce; + super(lv); + this.filename = fn; + this.file_.open(this.filename, "a"); + } + + /** A constructor for the $(D FileLogger) Logger that takes a reference to + a $(D File). + + The $(D File) passed must be open for all the log call to the + $(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger) + for logging will result in undefined behaviour. + + Params: + file = The file used for logging. + lv = The $(D LogLevel) for the $(D FileLogger). By default the + $(D LogLevel) for $(D FileLogger) is $(D LogLevel.info). + + Example: + ------------- + auto file = File("logFile.log", "w"); + auto l1 = new FileLogger(file, "LoggerName"); + auto l2 = new FileLogger(file, "LoggerName", LogLevel.fatal); + ------------- + */ + this(File file, const LogLevel lv = LogLevel.info) @safe + { + super(lv); + this.file_ = file; + } + + /** If the $(D FileLogger) is managing the $(D File) it logs to, this + method will return a reference to this File. + */ + @property File file() @safe + { + return this.file_; + } + + /* This method overrides the base class method in order to log to a file + without requiring heap allocated memory. Additionally, the $(D FileLogger) + local mutex is logged to serialize the log calls. + */ + override protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + import std.string : lastIndexOf; + ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; + ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; + + auto lt = this.file_.lockingTextWriter(); + systimeToISOString(lt, timestamp); + formattedWrite(lt, ":%s:%s:%u ", file[fnIdx .. $], + funcName[funIdx .. $], line); + } + + /* This methods overrides the base class method and writes the parts of + the log call directly to the file. + */ + override protected void logMsgPart(const(char)[] msg) + { + formattedWrite(this.file_.lockingTextWriter(), "%s", msg); + } + + /* This methods overrides the base class method and finalizes the active + log call. This requires flushing the $(D File) and releasing the + $(D FileLogger) local mutex. + */ + override protected void finishLogMsg() + { + this.file_.lockingTextWriter().put("\n"); + this.file_.flush(); + } + + /* This methods overrides the base class method and delegates the + $(D LogEntry) data to the actual implementation. + */ + override protected void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + + /** If the $(D FileLogger) was constructed with a filename, this method + returns this filename. Otherwise an empty $(D string) is returned. + */ + string getFilename() + { + return this.filename; + } + + private File file_; + private string filename; +} + +unittest +{ + import std.file : remove; + import std.array : empty; + import std.string : indexOf; + + string filename = randomString(32) ~ ".tempLogFile"; + auto l = new FileLogger(filename); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + destroy(l); + + auto file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); +} + +unittest +{ + import std.file : remove; + import std.array : empty; + import std.string : indexOf; + + string filename = randomString(32) ~ ".tempLogFile"; + auto file = File(filename, "w"); + auto l = new FileLogger(file); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + file.close(); + + file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); + file.close(); +} + +@safe unittest +{ + auto dl = cast(FileLogger)sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger)stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/std/experimental/logger/multilogger.d b/std/experimental/logger/multilogger.d new file mode 100644 index 00000000000..18ec7213dcf --- /dev/null +++ b/std/experimental/logger/multilogger.d @@ -0,0 +1,195 @@ +module std.experimental.logger.multilogger; + +import std.experimental.logger.core; +import std.experimental.logger.filelogger; + +/** This Element is stored inside the $(D MultiLogger) and associates a +$(D Logger) to a $(D string). +*/ +struct MultiLoggerEntry +{ + string name; /// The name if the $(D Logger) + Logger logger; /// The stored $(D Logger) +} + +/** MultiLogger logs to multiple $(D Logger). The $(D Logger)s are stored in an +$(D Logger[]) in there order of insertion. + +Every data logged to this $(D MultiLogger) will be distributed to all the $(D +Logger)s inserted into inserted it. This $(D MultiLogger) implementation can +hold multiple $(D Logger)s with the same name. If the method $(D removeLogger) +is used to remove a $(D Logger) only the first occurrence with that name will +be removed. +*/ +class MultiLogger : Logger +{ + /** A constructor for the $(D MultiLogger) Logger. + + Params: + lv = The $(D LogLevel) for the $(D MultiLogger). By default the + $(D LogLevel) for $(D MultiLogger) is $(D LogLevel.info). + + Example: + ------------- + auto l1 = new MultiLogger(LogLevel.trace); + ------------- + */ + this(const LogLevel lv = LogLevel.info) @safe + { + super(lv); + } + + /** This member holds all $(D Logger) stored in the $(D MultiLogger). + + When inheriting from $(D MultiLogger) this member can be used to gain + access to the stored $(D Logger). + */ + protected MultiLoggerEntry[] logger; + + /** This method inserts a new Logger into the $(D MultiLogger). + + Params: + name = The name of the $(D Logger) to insert. + newLogger = The $(D Logger) to insert. + */ + void insertLogger(string name, Logger newLogger) @safe + { + this.logger ~= MultiLoggerEntry(name, newLogger); + } + + /** This method removes a Logger from the $(D MultiLogger). + + Params: + toRemove = The name of the $(D Logger) to remove. If the $(D Logger) + is not found $(D null) will be returned. Only the first occurrence of + a $(D Logger) with the given name will be removed. + + Returns: The removed $(D Logger). + */ + Logger removeLogger(in char[] toRemove) @safe + { + import std.algorithm : copy; + import std.range.interfaces : back, popBack; + for (size_t i = 0; i < this.logger.length; ++i) + { + if (this.logger[i].name == toRemove) + { + Logger ret = this.logger[i].logger; + this.logger[i] = this.logger.back(); + this.logger.popBack(); + + return ret; + } + } + + return null; + } + + /* The override to pass the payload to all children of the + $(D MultiLoggerBase). + */ + override protected void writeLogMsg(ref LogEntry payload) @safe + { + foreach (it; this.logger) + { + /* We don't perform any checks here to avoid race conditions. + Instead the child will check on its own if its log level matches + and assume LogLevel.all for the globalLogLevel (since we already + know the message passes this test). + */ + it.logger.forwardMsg(payload); + } + } +} + +@safe unittest +{ + import std.experimental.logger.nulllogger; + import std.exception : assertThrown; + auto a = new MultiLogger; + auto n0 = new NullLogger(); + auto n1 = new NullLogger(); + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + auto n0_1 = a.removeLogger("zero"); + assert(n0_1 is n0); + auto n = a.removeLogger("zero"); + assert(n is null); + + auto n1_1 = a.removeLogger("one"); + assert(n1_1 is n1); + n = a.removeLogger("one"); + assert(n is null); +} + +@safe unittest +{ + auto a = new MultiLogger; + auto n0 = new TestLogger; + auto n1 = new TestLogger; + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + a.log("Hello TestLogger"); int line = __LINE__; + assert(n0.msg == "Hello TestLogger"); + assert(n0.line == line); + assert(n1.msg == "Hello TestLogger"); + assert(n0.line == line); +} + +// Issue #16 +unittest +{ + import std.stdio : File; + import std.string : indexOf; + auto logName = randomString(32) ~ ".log"; + auto logFileOutput = File(logName, "w"); + scope(exit) + { + import std.file : remove; + logFileOutput.close(); + remove(logName); + } + auto traceLog = new FileLogger(logFileOutput, LogLevel.all); + auto infoLog = new TestLogger(LogLevel.info); + + auto root = new MultiLogger(LogLevel.all); + root.insertLogger("fileLogger", traceLog); + root.insertLogger("stdoutLogger", infoLog); + + string tMsg = "A trace message"; + root.trace(tMsg); int line1 = __LINE__; + + assert(infoLog.line != line1); + assert(infoLog.msg != tMsg); + + string iMsg = "A info message"; + root.info(iMsg); int line2 = __LINE__; + + assert(infoLog.line == line2); + assert(infoLog.msg == iMsg, infoLog.msg ~ ":" ~ iMsg); + + logFileOutput.close(); + logFileOutput = File(logName, "r"); + assert(logFileOutput.isOpen); + assert(!logFileOutput.eof); + + auto line = logFileOutput.readln(); + assert(line.indexOf(tMsg) != -1, line ~ ":" ~ tMsg); + assert(!logFileOutput.eof); + line = logFileOutput.readln(); + assert(line.indexOf(iMsg) != -1, line ~ ":" ~ tMsg); +} + +@safe unittest +{ + auto dl = cast(FileLogger)sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger)stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/std/experimental/logger/nulllogger.d b/std/experimental/logger/nulllogger.d new file mode 100644 index 00000000000..bfa7981144e --- /dev/null +++ b/std/experimental/logger/nulllogger.d @@ -0,0 +1,36 @@ +module std.experimental.logger.nulllogger; + +import std.experimental.logger.core; + +/** The $(D NullLogger) will not process any log messages. + +In case of a log message with $(D LogLevel.fatal) nothing will happen. +*/ +class NullLogger : Logger +{ + /** The default constructor for the $(D NullLogger). + + Independent of the parameter this Logger will never log a message. + + Params: + lv = The $(D LogLevel) for the $(D NullLogger). By default the $(D LogLevel) + for $(D NullLogger) is $(D LogLevel.info). + */ + this(const LogLevel lv = LogLevel.info) @safe + { + super(lv); + this.fatalHandler = delegate() {}; + } + + override protected void writeLogMsg(ref LogEntry payload) @safe @nogc + { + } +} + +/// +@safe unittest +{ + auto nl1 = new NullLogger(LogLevel.all); + nl1.info("You will never read this."); + nl1.fatal("You will never read this, either and it will not throw"); +} diff --git a/std/experimental/logger/package.d b/std/experimental/logger/package.d new file mode 100644 index 00000000000..ee6adc2cb8f --- /dev/null +++ b/std/experimental/logger/package.d @@ -0,0 +1,182 @@ +/** +Implements logging facilities. + +Copyright: Copyright Robert "burner" Schadek 2013 -- +License: Boost License 1.0. +Authors: $(WEB http://www.svs.informatik.uni-oldenburg.de/60865.html, Robert burner Schadek) + +$(H3 Basic Logging) + +Message logging is a common approach to expose runtime information of a +program. Logging should be easy, but also flexible and powerful, therefore +$(D D) provides a standard interface for logging. + +The easiest way to create a log message is to write: +------------- +import std.experimental.logger; + +void main() { + log("Hello World"); +} +------------- +This will print a message to the $(D stderr) device. The message will contain +the filename, the linenumber, the name of the surrounding function, the time +and the message. + +More complex log call can go along the lines like: +------------- +log("Logging to the sharedLog with its default LogLevel"); +logf(LogLevel.info, 5 < 6, "%s to the sharedLog with its LogLevel.info", "Logging"); +info("Logging to the sharedLog with its info LogLevel"); +warning(5 < 6, "Logging to the sharedLog with its LogLevel.warning if 5 is less than 6"); +error("Logging to the sharedLog with its error LogLevel"); +errorf("Logging %s the sharedLog %s its error LogLevel", "to", "with"); +critical("Logging to the"," sharedLog with its error LogLevel"); +fatal("Logging to the sharedLog with its fatal LogLevel"); + +auto fLogger = new FileLogger("NameOfTheLogFile"); +fLogger.log("Logging to the fileLogger with its default LogLevel"); +fLogger.info("Logging to the fileLogger with its default LogLevel"); +fLogger.warning(5 < 6, "Logging to the fileLogger with its LogLevel.warning if 5 is less than 6"); +fLogger.warningf(5 < 6, "Logging to the fileLogger with its LogLevel.warning if %s is %s than 6", 5, "less"); +fLogger.critical("Logging to the fileLogger with its info LogLevel"); +fLogger.log(LogLevel.trace, 5 < 6, "Logging to the fileLogger"," with its default LogLevel if 5 is less than 6"); +fLogger.fatal("Logging to the fileLogger with its warning LogLevel"); +------------- +Additionally, this example shows how a new $(D FileLogger) is created. +Indivitual $(D Logger) and the global log functions share commonly named +functions to log data. + +The names of the functions are as follows: +$(LI $(D log)) +$(LI $(D trace)) +$(LI $(D info)) +$(LI $(D warning)) +$(LI $(D critical)) +$(LI $(D fatal)) +The default $(D Logger) will by default log to $(D stderr) and has a default +$(D LogLevel) of $(D LogLevel.all). The default Logger can be accessed by +using the property called $(D sharedLog). This property a reference to the +current default $(D Logger). This reference can be used to assign a new +default $(D Logger). +------------- +sharedLog = new FileLogger("New_Default_Log_File.log"); +------------- + +Additional $(D Logger) can be created by creating a new instance of the +required $(D Logger). + +$(H3 Logging Fundamentals) +$(H4 LogLevel) +The $(D LogLevel) of an log call can be defined in two ways. The first is by +calling $(D log) and passing the $(D LogLevel) explicit as the first argument. +The second way of setting the $(D LogLevel) of a +log call, is by calling either $(D trace), $(D info), $(D warning), +$(D critical), or $(D fatal). The log call will than have the respective +$(D LogLevel). If no $(D LogLevel) is defined the log call will use the +current $(D LogLevel) of the used $(D Logger). If data is logged with +$(D LogLevel) $(D fatal) by default an $(D Error) will be thrown. +This behaviour can be modified by using the member $(D fatalHandler) to +assign a custom delegate to handle log call with $(D LogLevel) $(D fatal). + +$(H4 Conditional Logging) +Conditional logging can be achieved be passing a $(D bool) as first +argument to a log function. If conditional logging is used the condition must +be $(D true) in order to have the log message logged. + +In order to combine an explicit $(D LogLevel) passing with conditional +logging, the $(D LogLevel) has to be passed as first argument followed by the +$(D bool). + +$(H4 Filtering Log Messages) +Messages are logged if the $(D LogLevel) of the log message is greater than or +equal to than the $(D LogLevel) of the used $(D Logger) and additionally if the +$(D LogLevel) of the log message is greater equal to the global $(D LogLevel). +If a condition is passed into the log call, this condition must be true. + +The global $(D LogLevel) is accessible by using $(D globalLogLevel). +To assign the $(D LogLevel) of a $(D Logger) use the $(D logLevel) property of +the logger. + +$(H4 Printf Sytle Logging) +If $(D printf)-style logging is needed add a $(B f) to the logging call, such as +$(D myLogger.infof("Hello %s", "world");) or $(fatalf("errno %d", 1337)) +The additional $(B f) enables $(D printf)-style logging for call combinations of +explicit $(D LogLevel) and conditional logging functions and methods. + +$(H4 Thread Local Redirection) +Calls to the free standing log functions are not directly forwarded to the +global $(D Logger) $(D sharedLog). Actually, a thread local $(D Logger) of +type $(D StdForwardLogger) process the log call and then, by default, forward +the created $(D Logger.LogEntry) to the $(D sharedLog) $(D Logger). +The thread local $(D Logger) is accessable by the $(D stdThreadLocalLog) +property. This property allows to assign user defined $(D Logger). The default +$(D LogLevel) of the $(D stdThreadLocalLog) $(D Logger) is $(D LogLevel.all) +and it will therefore forward all messaged to the $(D sharedLog) $(D Logger). +The $(D LogLevel) of the $(D stdThreadLocalLog) can be used to filter log +calls before they reach the $(D sharedLog) $(D Logger). + +$(H3 User Defined Logger) +To customize the $(D Logger) behavior, create a new $(D class) that inherits from +the abstract $(D Logger) $(D class), and implements the $(D writeLogMsg) +method. +------------- +class MyCustomLogger : Logger +{ + this(string newName, LogLevel lv) @safe + { + super(newName, lv); + } + + override void writeLogMsg(ref LogEntry payload) + { + // log message in my custom way + } +} + +auto logger = new MyCustomLogger(); +logger.log("Awesome log message"); +------------- + +To gain more precise control over the logging process, additionally to +overwriting the $(D writeLogMsg) method the methods $(D beginLogMsg), +$(D logMsgPart) and $(D finishLogMsg) can be overwritten. + +$(H3 Compile Time Disabeling of $(D Logger)) +In order to disable logging at compile time, pass $(D StdLoggerDisableLogging) as a +version argument to the $(D D) compiler when compiling your program code. +This will disable all logging functionality. +Specific $(D LogLevel) can be disabled at compile time as well. +In order to disable logging with the $(D trace) $(D LogLevel) pass +$(D StdLoggerDisableTrace) as a version. +The following table shows which version statement disables which +$(D LogLevel). +$(TABLE + $(TR $(TD $(D LogLevel.trace) ) $(TD StdLoggerDisableTrace)) + $(TR $(TD $(D LogLevel.info) ) $(TD StdLoggerDisableInfo)) + $(TR $(TD $(D LogLevel.warning) ) $(TD StdLoggerDisableWarning)) + $(TR $(TD $(D LogLevel.error) ) $(TD StdLoggerDisableError)) + $(TR $(TD $(D LogLevel.critical) ) $(TD StdLoggerDisableCritical)) + $(TR $(TD $(D LogLevel.fatal) ) $(TD StdLoggerDisableFatal)) +) +Such a version statement will only disable logging in the associated compile +unit. + +$(H3 Provided Logger) +By default four $(D Logger) implementations are given. The $(D FileLogger) +logs data to files. It can also be used to log to $(D stdout) and $(D stderr) +as these devices are files as well. A $(D Logger) that logs to $(D stdout) can +therefore be created by $(D new FileLogger(stdout)). +The $(D MultiLogger) is basically an associative array of $(D string)s to +$(D Logger). It propagates log calls to its stored $(D Logger). The +$(D ArrayLogger) contains an array of $(D Logger) and also propagates log +calls to its stored $(D Logger). The $(D NullLogger) does not do anything. It +will never log a message and will never throw on a log call with $(D LogLevel) +$(D error). +*/ +module std.experimental.logger; + +public import std.experimental.logger.core; +public import std.experimental.logger.filelogger; +public import std.experimental.logger.nulllogger; +public import std.experimental.logger.multilogger; diff --git a/std/file.d b/std/file.d index fad8d28d3d3..fe37d741663 100644 --- a/std/file.d +++ b/std/file.d @@ -18,16 +18,18 @@ Source: $(PHOBOSSRC std/_file.d) */ module std.file; -import core.memory; -import core.stdc.stdio, core.stdc.stdlib, core.stdc.string, - core.stdc.errno, std.algorithm, std.array, std.conv, - std.datetime, std.exception, std.format, std.path, std.process, - std.range, std.stdio, std.string, std.traits, - std.typecons, std.typetuple, std.utf; - +import core.stdc.stdlib, core.stdc.string, core.stdc.errno; + +import std.conv; +import std.datetime; +import std.exception; +import std.path; +import std.range.primitives; +import std.traits; +import std.typecons; +import std.typetuple; import std.internal.cstring; - version (Windows) { import core.sys.windows.windows, std.windows.syserror; @@ -42,10 +44,9 @@ else version (unittest) { - import core.thread; - @property string deleteme() @safe { + import std.process : thisProcessID; static _deleteme = "deleteme.dmd.unittest.pid"; static _first = true; @@ -190,125 +191,104 @@ Returns: Untyped array of bytes _read. Throws: $(D FileException) on error. */ -void[] read(in char[] name, size_t upTo = size_t.max) @safe +version (Posix) void[] read(in char[] name, size_t upTo = size_t.max) @trusted +{ + import std.algorithm : min; + import std.array : uninitializedArray; + import core.memory : GC; + + // A few internal configuration parameters { + enum size_t + minInitialAlloc = 1024 * 4, + maxInitialAlloc = size_t.max / 2, + sizeIncrement = 1024 * 16, + maxSlackMemoryAllowed = 1024; + // } + + immutable fd = core.sys.posix.fcntl.open(name.tempCString, + core.sys.posix.fcntl.O_RDONLY); + cenforce(fd != -1, name); + scope(exit) core.sys.posix.unistd.close(fd); + + stat_t statbuf = void; + cenforce(fstat(fd, &statbuf) == 0, name); + + immutable initialAlloc = to!size_t(statbuf.st_size + ? min(statbuf.st_size + 1, maxInitialAlloc) + : minInitialAlloc); + void[] result = uninitializedArray!(ubyte[])(initialAlloc); + scope(failure) delete result; + size_t size = 0; + + for (;;) + { + immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size, + min(result.length, upTo) - size); + cenforce(actual != -1, name); + if (actual == 0) break; + size += actual; + if (size < result.length) continue; + immutable newAlloc = size + sizeIncrement; + result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)[0 .. newAlloc]; + } + + return result.length - size >= maxSlackMemoryAllowed + ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size] + : result[0 .. size]; +} + +version (Windows) void[] read(in char[] name, size_t upTo = size_t.max) @safe { + import std.algorithm : min; + import std.array : uninitializedArray; static trustedRef(T)(ref T buf) @trusted { return &buf; } - version(Windows) - { - static trustedCreateFileW(in char[] fileName, DWORD dwDesiredAccess, DWORD dwShareMode, - SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted - { - return CreateFileW(fileName.tempCStringW(), dwDesiredAccess, dwShareMode, - lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); - } - static trustedCloseHandle(HANDLE hObject) @trusted - { - return CloseHandle(hObject); - } - static trustedGetFileSize(HANDLE hFile, DWORD *lpFileSizeHigh) @trusted - { - return GetFileSize(hFile, lpFileSizeHigh); - } - static trustedReadFile(HANDLE hFile, void *lpBuffer, DWORD nNumberOfBytesToRead, - DWORD *lpNumberOfBytesRead, OVERLAPPED *lpOverlapped) @trusted - { - return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, - lpNumberOfBytesRead, lpOverlapped); - } - - alias defaults = - TypeTuple!(GENERIC_READ, - FILE_SHARE_READ, (SECURITY_ATTRIBUTES*).init, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, - HANDLE.init); - auto h = trustedCreateFileW(name, defaults); + static trustedCreateFileW(in char[] fileName, DWORD dwDesiredAccess, DWORD dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted + { + return CreateFileW(fileName.tempCStringW(), dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); - cenforce(h != INVALID_HANDLE_VALUE, name); - scope(exit) cenforce(trustedCloseHandle(h), name); - auto size = trustedGetFileSize(h, null); - cenforce(size != INVALID_FILE_SIZE, name); - size = min(upTo, size); - auto buf = uninitializedArray!(ubyte[])(size); - scope(failure) delete buf; - - DWORD numread = void; - cenforce(trustedReadFile(h,buf.ptr, size, trustedRef(numread), null) != 0 - && numread == size, name); - return buf[0 .. size]; } - else version(Posix) + static trustedCloseHandle(HANDLE hObject) @trusted { - // A few internal configuration parameters { - enum size_t - minInitialAlloc = 1024 * 4, - maxInitialAlloc = size_t.max / 2, - sizeIncrement = 1024 * 16, - maxSlackMemoryAllowed = 1024; - // } - - static trustedOpen(in char[] path, int oflag) @trusted - { - return core.sys.posix.fcntl.open(path.tempCString(), oflag); - } - static trustedFstat(int path, stat_t* buf) @trusted - { - return fstat(path, buf); - } - static trustedRead(int fildes, void* buf, size_t nbyte) @trusted - { - return core.sys.posix.unistd.read(fildes, buf, nbyte); - } - static trustedRealloc(void* p, size_t sz, uint ba = 0, const TypeInfo ti = null) @trusted - { - return GC.realloc(p, sz, ba, ti); - } - static trustedPtrAdd(void[] buf, size_t s) @trusted - { - return buf.ptr+s; - } - static trustedPtrSlicing(void* ptr, size_t lb, size_t ub) @trusted - { - return ptr[lb..ub]; - } - - immutable fd = trustedOpen(name, - core.sys.posix.fcntl.O_RDONLY); - cenforce(fd != -1, name); - scope(exit) core.sys.posix.unistd.close(fd); - - stat_t statbuf = void; - cenforce(trustedFstat(fd, trustedRef(statbuf)) == 0, name); + return CloseHandle(hObject); + } + static trustedGetFileSize(HANDLE hFile, DWORD *lpFileSizeHigh) @trusted + { + return GetFileSize(hFile, lpFileSizeHigh); + } + static trustedReadFile(HANDLE hFile, void *lpBuffer, DWORD nNumberOfBytesToRead, + DWORD *lpNumberOfBytesRead, OVERLAPPED *lpOverlapped) @trusted + { + return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, + lpNumberOfBytesRead, lpOverlapped); + } - immutable initialAlloc = to!size_t(statbuf.st_size - ? min(statbuf.st_size + 1, maxInitialAlloc) - : minInitialAlloc); - void[] result = uninitializedArray!(ubyte[])(initialAlloc); - scope(failure) delete result; - size_t size = 0; + alias defaults = + TypeTuple!(GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, (SECURITY_ATTRIBUTES*).init, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + HANDLE.init); + auto h = trustedCreateFileW(name, defaults); - for (;;) - { - immutable actual = trustedRead(fd, trustedPtrAdd(result, size), - min(result.length, upTo) - size); - cenforce(actual != -1, name); - if (actual == 0) break; - size += actual; - if (size < result.length) continue; - immutable newAlloc = size + sizeIncrement; - result = trustedPtrSlicing(trustedRealloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN), - 0, newAlloc); - } + cenforce(h != INVALID_HANDLE_VALUE, name); + scope(exit) cenforce(trustedCloseHandle(h), name); + auto size = trustedGetFileSize(h, null); + cenforce(size != INVALID_FILE_SIZE, name); + size = min(upTo, size); + auto buf = uninitializedArray!(ubyte[])(size); + scope(failure) delete buf; - return result.length - size >= maxSlackMemoryAllowed - ? trustedPtrSlicing(trustedRealloc(result.ptr, size, GC.BlkAttr.NO_SCAN), 0, size) - : result[0 .. size]; - } + DWORD numread = void; + cenforce(trustedReadFile(h,buf.ptr, size, trustedRef(numread), null) != 0 + && numread == size, name); + return buf[0 .. size]; } @safe unittest @@ -327,6 +307,15 @@ version (linux) @safe unittest //writefln("'%s'", s); } +@safe unittest +{ + scope(exit) if (exists(deleteme)) remove(deleteme); + import std.stdio; + auto f = File(deleteme, "w"); + f.write("abcd"); f.flush(); + assert(read(deleteme) == "abcd"); +} + /******************************************** Read and validates (using $(XREF utf, validate)) a text file. $(D S) can be a type of array of characters of any width and constancy. No @@ -350,14 +339,16 @@ enforce(chomp(readText("deleteme")) == "abc"); S readText(S = string)(in char[] name) @safe if (isSomeString!S) { + import std.utf : validate; static auto trustedCast(void[] buf) @trusted { return cast(S)buf; } auto result = trustedCast(read(name)); - std.utf.validate(result); + validate(result); return result; } @safe unittest { + import std.string; write(deleteme, "abc\n"); scope(exit) { assert(exists(deleteme)); remove(deleteme); } enforce(chomp(readText(deleteme)) == "abc"); @@ -473,7 +464,11 @@ void rename(in char[] from, in char[] to) @trusted to))); } else version(Posix) + { + import core.stdc.stdio; + cenforce(core.stdc.stdio.rename(from.tempCString(), to.tempCString()) == 0, to); + } } @safe unittest @@ -500,8 +495,12 @@ void remove(in char[] name) @trusted cenforce(DeleteFileW(name.tempCStringW()), name); } else version(Posix) + { + import core.stdc.stdio; + cenforce(core.stdc.stdio.remove(name.tempCString()) == 0, "Failed to remove file " ~ name); + } } version(Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(in char[] name) @trusted @@ -537,7 +536,7 @@ ulong getSize(in char[] name) @safe { return stat(path.tempCString(), buf); } - static stat_t* ptrOfLocalVariable(ref stat_t buf) @trusted + static stat_t* ptrOfLocalVariable(return ref stat_t buf) @trusted { return &buf; } @@ -599,6 +598,8 @@ void getTimes(in char[] name, unittest { + import std.stdio : writefln; + auto currTime = Clock.currTime(); write(deleteme, "a"); @@ -622,6 +623,7 @@ unittest version(fullFileTests) { + import core.thread; enum sleepTime = dur!"seconds"(2); Thread.sleep(sleepTime); @@ -685,6 +687,7 @@ else version(Windows) void getTimesWin(in char[] name, version(Windows) unittest { + import std.stdio : writefln; auto currTime = Clock.currTime(); write(deleteme, "a"); @@ -716,6 +719,7 @@ version(Windows) unittest version(fullFileTests) { + import core.thread; Thread.sleep(dur!"seconds"(2)); currTime = Clock.currTime(); @@ -820,6 +824,7 @@ void setTimes(in char[] name, unittest { + import std.stdio : File; string newdir = deleteme ~ r".dir"; string dir = newdir ~ r"/a/b/c"; string file = dir ~ "/file"; @@ -1021,21 +1026,29 @@ bool exists(in char[] name) @trusted nothrow @nogc Throws: $(D FileException) on error. +/ -uint getAttributes(in char[] name) +uint getAttributes(in char[] name) @safe { version(Windows) { - immutable result = GetFileAttributesW(name.tempCStringW()); + static auto trustedGetFileAttributesW(in char[] fileName) @trusted + { + return GetFileAttributesW(fileName.tempCStringW()); + } + immutable result = trustedGetFileAttributesW(name); - enforce(result != uint.max, new FileException(name.idup)); + cenforce(result != INVALID_FILE_ATTRIBUTES, name); return result; } else version(Posix) { + static auto trustedStat(in char[] path, ref stat_t buf) @trusted + { + return stat(path.tempCString(), &buf); + } stat_t statbuf = void; - cenforce(stat(name.tempCString(), &statbuf) == 0, name); + cenforce(trustedStat(name, statbuf) == 0, name); return statbuf.st_mode; } @@ -1058,7 +1071,7 @@ uint getAttributes(in char[] name) Throws: $(D FileException) on error. +/ -uint getLinkAttributes(in char[] name) +uint getLinkAttributes(in char[] name) @safe { version(Windows) { @@ -1066,8 +1079,12 @@ uint getLinkAttributes(in char[] name) } else version(Posix) { + static auto trustedLstat(in char[] path, ref stat_t buf) @trusted + { + return lstat(path.tempCString(), &buf); + } stat_t lstatbuf = void; - cenforce(lstat(name.tempCString(), &lstatbuf) == 0, name); + cenforce(trustedLstat(name, lstatbuf) == 0, name); return lstatbuf.st_mode; } } @@ -1079,16 +1096,24 @@ uint getLinkAttributes(in char[] name) Throws: $(D FileException) if the given file does not exist. +/ -void setAttributes(in char[] name, uint attributes) +void setAttributes(in char[] name, uint attributes) @safe { version (Windows) { - cenforce(SetFileAttributesW(name.tempCStringW(), attributes), name); + static auto trustedSetFileAttributesW(in char[] fileName, uint dwFileAttributes) @trusted + { + return SetFileAttributesW(fileName.tempCStringW(), dwFileAttributes); + } + cenforce(trustedSetFileAttributesW(name, attributes), name); } else version (Posix) { + static auto trustedChmod(in char[] path, mode_t mode) @trusted + { + return chmod(path.tempCString(), mode); + } assert(attributes <= mode_t.max); - cenforce(!chmod(name.tempCString(), cast(mode_t)attributes), name); + cenforce(!trustedChmod(name, cast(mode_t)attributes), name); } } @@ -1108,7 +1133,7 @@ assert(!"/etc/fonts/fonts.conf".isDir); assert("/usr/share/include".isDir); -------------------- +/ -@property bool isDir(in char[] name) +@property bool isDir(in char[] name) @safe { version(Windows) { @@ -1120,7 +1145,7 @@ assert("/usr/share/include".isDir); } } -unittest +@safe unittest { version(Windows) { @@ -1165,7 +1190,7 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc } } -unittest +@safe unittest { version(Windows) { @@ -1225,7 +1250,7 @@ assert("/etc/fonts/fonts.conf".isFile); assert(!"/usr/share/include".isFile); -------------------- +/ -@property bool isFile(in char[] name) +@property bool isFile(in char[] name) @safe { version(Windows) return !name.isDir; @@ -1233,7 +1258,7 @@ assert(!"/usr/share/include".isFile); return (getAttributes(name) & S_IFMT) == S_IFREG; } -unittest +@safe unittest { version(Windows) { @@ -1289,7 +1314,7 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc } } -unittest +@safe unittest { version(Windows) { @@ -1334,7 +1359,7 @@ unittest Throws: $(D FileException) if the given file does not exist. +/ -@property bool isSymlink(C)(const(C)[] name) +@property bool isSymlink(in char[] name) @safe { version(Windows) return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0; @@ -1411,6 +1436,8 @@ unittest assert(!attrIsFile(getLinkAttributes(symfile))); } } + + static assert(__traits(compiles, () @safe { return "dummy".isSymlink; })); } @@ -1444,36 +1471,49 @@ bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc * Change directory to $(D pathname). * Throws: $(D FileException) on error. */ -void chdir(in char[] pathname) +void chdir(in char[] pathname) @safe { version(Windows) { - enforce(SetCurrentDirectoryW(pathname.tempCStringW()), - new FileException(pathname.idup)); + static auto trustedSetCurrentDirectoryW(in char[] path) @trusted + { + return SetCurrentDirectoryW(path.tempCStringW()); + } + cenforce(trustedSetCurrentDirectoryW(pathname), pathname); } else version(Posix) { - cenforce(core.sys.posix.unistd.chdir(pathname.tempCString()) == 0, - pathname); + static auto trustedChdir(in char[] path) @trusted + { + return core.sys.posix.unistd.chdir(path.tempCString()); + } + cenforce(trustedChdir(pathname) == 0, pathname); } } /**************************************************** Make directory $(D pathname). -Throws: $(D FileException) on error. +Throws: $(D FileException) on Posix or $(D WindowsException) on Windows + if an error occured. */ -void mkdir(in char[] pathname) +void mkdir(in char[] pathname) @safe { version(Windows) { - enforce(CreateDirectoryW(pathname.tempCStringW(), null), - new FileException(pathname.idup)); + static auto trustedCreateDirectoryW(in char[] path) @trusted + { + return CreateDirectoryW(path.tempCStringW(), null); + } + wenforce(trustedCreateDirectoryW(pathname), pathname); } else version(Posix) { - cenforce(core.sys.posix.sys.stat.mkdir(pathname.tempCString(), octal!777) == 0, - pathname); + static auto trustedMkdir(in char[] path, mode_t mode) @trusted + { + return core.sys.posix.sys.stat.mkdir(path.tempCString(), mode); + } + cenforce(trustedMkdir(pathname, octal!777) == 0, pathname); } } @@ -1597,15 +1637,18 @@ void rmdir(in char[] pathname) $(D FileException) on error (which includes if the symlink already exists). +/ -version(StdDdoc) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link); -else version(Posix) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link) +version(StdDdoc) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link) @safe; +else version(Posix) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link) @safe { - cenforce(core.sys.posix.unistd.symlink(original.tempCString(), - link.tempCString()) == 0, - link); + static auto trustedSymlink(const(C1)[] path1, const(C2)[] path2) @trusted + { + return core.sys.posix.unistd.symlink(path1.tempCString(), + path2.tempCString()); + } + cenforce(trustedSymlink(original, link) == 0, link); } -version(Posix) unittest +version(Posix) @safe unittest { if(system_directory.exists) { @@ -1660,16 +1703,22 @@ version(Posix) unittest Throws: $(D FileException) on error. +/ -version(StdDdoc) string readLink(C)(const(C)[] link); -else version(Posix) string readLink(C)(const(C)[] link) +version(StdDdoc) string readLink(C)(const(C)[] link) @safe; +else version(Posix) string readLink(C)(const(C)[] link) @safe { + static auto trustedReadlink(const(C)[] path, char[] buf) @trusted + { + return core.sys.posix.unistd.readlink(path.tempCString(), buf.ptr, buf.length); + } + static auto trustedAssumeUnique(ref C[] array) @trusted + { + return assumeUnique(array); + } + enum bufferLen = 2048; enum maxCodeUnits = 6; char[bufferLen] buffer; - auto linkTmp = link.tempCString(); - auto size = core.sys.posix.unistd.readlink(linkTmp, - buffer.ptr, - buffer.length); + auto size = trustedReadlink(link, buffer); cenforce(size != -1, link); if(size <= bufferLen - maxCodeUnits) @@ -1679,15 +1728,13 @@ else version(Posix) string readLink(C)(const(C)[] link) foreach(i; 0 .. 10) { - size = core.sys.posix.unistd.readlink(linkTmp, - dynamicBuffer.ptr, - dynamicBuffer.length); + size = trustedReadlink(link, dynamicBuffer); cenforce(size != -1, link); if(size <= dynamicBuffer.length - maxCodeUnits) { dynamicBuffer.length = size; - return assumeUnique(dynamicBuffer); + return trustedAssumeUnique(dynamicBuffer); } dynamicBuffer.length = dynamicBuffer.length * 3 / 2; @@ -1696,8 +1743,9 @@ else version(Posix) string readLink(C)(const(C)[] link) throw new FileException(to!string(link), "Path is too long to read."); } -version(Posix) unittest +version(Posix) @safe unittest { + import std.string; foreach(file; [system_directory, system_file]) { if(file.exists) @@ -1720,6 +1768,7 @@ version(Posix) unittest */ version(Windows) string getcwd() { + import std.utf : toUTF8; /* GetCurrentDirectory's return value: 1. function succeeds: the number of characters that are written to the buffer, not including the terminating null character. @@ -1832,6 +1881,14 @@ else version (FreeBSD) return buffer.assumeUnique; } + else version (Solaris) + { + import core.sys.posix.unistd : getpid; + import std.string : format; + + // Only Solaris 10 and later + return readLink(format("/proc/%d/path/a.out", getpid())); + } else version (Android) { return readLink("/proc/self/exe"); @@ -1840,7 +1897,7 @@ else version (FreeBSD) static assert(0, "thisExePath is not supported on this platform"); } -unittest +@safe unittest { auto path = thisExePath(); @@ -2015,6 +2072,7 @@ else version(Windows) { struct DirEntry { + import std.utf : toUTF8; public: alias name this; @@ -2037,9 +2095,11 @@ else version(Windows) private this(string path, in WIN32_FIND_DATAW *fd) { - size_t clength = std.string.wcslen(fd.cFileName.ptr); - _name = std.utf.toUTF8(fd.cFileName[0 .. clength]); - _name = buildPath(path, std.utf.toUTF8(fd.cFileName[0 .. clength])); + import core.stdc.wchar_ : wcslen; + + size_t clength = wcslen(fd.cFileName.ptr); + _name = toUTF8(fd.cFileName[0 .. clength]); + _name = buildPath(path, toUTF8(fd.cFileName[0 .. clength])); _size = (cast(ulong)fd.nFileSizeHigh << 32) | fd.nFileSizeLow; _timeCreated = std.datetime.FILETIMEToSysTime(&fd.ftCreationTime); _timeLastAccessed = std.datetime.FILETIMEToSysTime(&fd.ftLastAccessTime); @@ -2146,13 +2206,21 @@ else version(Posix) //of DT_UNKNOWN in case we don't ever actually //need the dtype, thus potentially avoiding the //cost of calling lstat). - if(fd.d_type != DT_UNKNOWN) + static if (__traits(compiles, fd.d_type != DT_UNKNOWN)) { - _dType = fd.d_type; - _dTypeSet = true; + if(fd.d_type != DT_UNKNOWN) + { + _dType = fd.d_type; + _dTypeSet = true; + } + else + _dTypeSet = false; } else + { + // e.g. Solaris does not have the d_type member _dTypeSet = false; + } } @property string name() const pure nothrow @@ -2234,12 +2302,16 @@ else version(Posix) This is to support lazy evaluation, because doing stat's is expensive and not always needed. +/ - void _ensureStatDone() + void _ensureStatDone() @safe { + static auto trustedStat(in char[] path, stat_t* buf) @trusted + { + return stat(path.tempCString(), buf); + } if(_didStat) return; - enforce(stat(_name.tempCString(), &_statBuf) == 0, + enforce(trustedStat(_name, &_statBuf) == 0, "Failed to stat file `" ~ _name ~ "'"); _didStat = true; @@ -2382,22 +2454,43 @@ unittest } } +alias PreserveAttributes = Flag!"preserveAttributes"; + +version (StdDdoc) +{ + /// Defaults to PreserveAttributes.yes on Windows, and the opposite on all other platforms. + PreserveAttributes preserveAttributesDefault; +} +else version(Windows) +{ + enum preserveAttributesDefault = PreserveAttributes.yes; +} +else +{ + enum preserveAttributesDefault = PreserveAttributes.no; +} + /*************************************************** Copy file $(D from) to file $(D to). File timestamps are preserved. +File attributes are preserved, if $(D preserve) equals $(D PreserveAttributes.yes). +On Windows only $(D PreserveAttributes.yes) (the default on Windows) is supported. If the target file exists, it is overwritten. Throws: $(D FileException) on error. */ -void copy(in char[] from, in char[] to) +void copy(in char[] from, in char[] to, PreserveAttributes preserve = preserveAttributesDefault) { version(Windows) { + assert(preserve == Yes.preserve); immutable result = CopyFileW(from.tempCStringW(), to.tempCStringW(), false); if (!result) throw new FileException(to.idup); } else version(Posix) { + import core.stdc.stdio; + immutable fd = core.sys.posix.fcntl.open(from.tempCString(), O_RDONLY); cenforce(fd != -1, from); scope(exit) core.sys.posix.unistd.close(fd); @@ -2433,6 +2526,8 @@ void copy(in char[] from, in char[] to) assert(size >= toxfer); size -= toxfer; } + if (preserve) + cenforce(fchmod(fdw, statbuf.st_mode) == 0, from); } cenforce(core.sys.posix.unistd.close(fdw) != -1, from); @@ -2457,6 +2552,16 @@ unittest assert(readText(t2) == "2"); } +version(Posix) unittest //issue 11434 +{ + auto t1 = deleteme, t2 = deleteme~"2"; + scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); + write(t1, "1"); + setAttributes(t1, octal!767); + copy(t1, t2, Yes.preserveAttributes); + assert(readText(t2) == "1"); + assert(getAttributes(t2) == octal!100767); +} /++ Remove directory and all of its content and subdirectories, @@ -2587,6 +2692,7 @@ enum SpanMode private struct DirIteratorImpl { + import std.array : Appender, appender; SpanMode _mode; // Whether we should follow symlinked directories while iterating. // It also indicates whether we should avoid functions which call @@ -2637,6 +2743,8 @@ private struct DirIteratorImpl bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo) { + import core.stdc.wchar_ : wcscmp; + if(fetch) { if(FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE) @@ -2645,8 +2753,8 @@ private struct DirIteratorImpl return false; } } - while( std.string.wcscmp(findinfo.cFileName.ptr, ".") == 0 - || std.string.wcscmp(findinfo.cFileName.ptr, "..") == 0) + while( wcscmp(findinfo.cFileName.ptr, ".") == 0 + || wcscmp(findinfo.cFileName.ptr, "..") == 0) if(FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE) { popDirStack(); @@ -2862,12 +2970,15 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true) unittest { + import std.algorithm; + import std.range; + import std.process; version(Android) string testdir = deleteme; // This has to be an absolute path when // called from a shared library on Android, // ie an apk else - string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(getpid()); // needs to be relative + string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID); // needs to be relative mkdirRecurse(buildPath(testdir, "somedir")); scope(exit) rmdirRecurse(testdir); write(buildPath(testdir, "somefile"), null); @@ -2947,22 +3058,12 @@ foreach(d; dFiles) auto dirEntries(string path, string pattern, SpanMode mode, bool followSymlink = true) { + import std.algorithm : filter; bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); } return filter!f(DirIterator(path, mode, followSymlink)); } -/++ - $(RED Deprecated. It will be removed in July 2014. - Please use $(LREF DirEntry) constructor directly instead.) - - Returns a DirEntry for the given file (or directory). - - Params: - name = The file (or directory) to get a DirEntry for. - - Throws: - $(D FileException) if the file does not exist. - +/ +// Explicitly undocumented. It will be removed in July 2015. deprecated("Please use DirEntry constructor directly instead.") DirEntry dirEntry(in char[] name) { @@ -2972,6 +3073,8 @@ DirEntry dirEntry(in char[] name) //Test dirEntry with a directory. unittest { + import core.thread; + import std.stdio : writefln; auto before = Clock.currTime(); Thread.sleep(dur!"seconds"(2)); immutable path = deleteme ~ "_dir"; @@ -3022,6 +3125,8 @@ unittest //Test dirEntry with a file. unittest { + import core.thread; + import std.stdio : writefln; auto before = Clock.currTime(); Thread.sleep(dur!"seconds"(2)); immutable path = deleteme ~ "_file"; @@ -3072,6 +3177,8 @@ unittest //Test dirEntry with a symlink to a directory. version(linux) unittest { + import core.thread; + import std.stdio : writefln; auto before = Clock.currTime(); Thread.sleep(dur!"seconds"(2)); immutable orig = deleteme ~ "_dir"; @@ -3117,6 +3224,8 @@ version(linux) unittest //Test dirEntry with a symlink to a file. version(linux) unittest { + import core.thread; + import std.stdio : writefln; auto before = Clock.currTime(); Thread.sleep(dur!"seconds"(2)); immutable orig = deleteme ~ "_file"; @@ -3180,6 +3289,9 @@ a file name to prevent accidental changes to result in incorrect behavior. Select!(Types.length == 1, Types[0][], Tuple!(Types)[]) slurp(Types...)(string filename, in char[] format) { + import std.stdio : File; + import std.format : formattedRead; + import std.array : appender; typeof(return) result; auto app = appender!(typeof(return))(); ElementType!(typeof(return)) toAdd; @@ -3245,6 +3357,7 @@ string tempDir() @trusted { version(Windows) { + import std.utf : toUTF8; // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx wchar[MAX_PATH + 2] buf; DWORD len = GetTempPathW(buf.length, buf.ptr); @@ -3252,6 +3365,7 @@ string tempDir() @trusted } else version(Posix) { + import std.process : environment; // This function looks through the list of alternative directories // and returns the first one which exists and is a directory. static string findExistingDir(T...)(lazy T alternatives) diff --git a/std/format.d b/std/format.d index e1e2a2e99d7..48738c16fbc 100644 --- a/std/format.d +++ b/std/format.d @@ -24,22 +24,11 @@ module std.format; //debug=format; // uncomment to turn on debugging printf's -import core.stdc.stdio, core.stdc.stdlib, core.stdc.string, core.vararg; -import std.algorithm, std.ascii, std.conv, - std.exception, std.range, - std.system, std.traits, std.typetuple, - std.utf; -version (CRuntime_Microsoft) { - import std.math : isnan, isInfinity; -} -version(unittest) { - import std.math; - import std.stdio; - import std.string; - import std.typecons; - import core.exception; - import std.string; -} +import core.vararg; +import std.exception; +import std.range.primitives; +import std.traits; +import std.typetuple; version(CRuntime_DigitalMars) { @@ -313,11 +302,10 @@ $(I FormatChar): 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. - Examples: ------------------------- - import std.c.stdio; + import core.stdc.stdio; import std.format; void main() @@ -427,6 +415,8 @@ My friends are John, Nancy. */ uint formattedWrite(Writer, Char, A...)(Writer w, in Char[] fmt, A args) { + import std.conv : text, to; + alias FPfmt = void function(Writer, const(void)*, ref FormatSpec!Char) @safe pure nothrow; auto spec = FormatSpec!Char(fmt); @@ -455,7 +445,7 @@ uint formattedWrite(Writer, Char, A...)(Writer w, in Char[] fmt, A args) { // leftover spec? enforceFmt(fmt.length == 0, - text("Orphan format specifier: %", fmt)); + text("Orphan format specifier: %", spec.spec)); break; } if (spec.width == spec.DYNAMIC) @@ -532,30 +522,26 @@ uint formattedWrite(Writer, Char, A...)(Writer w, in Char[] fmt, A args) @safe pure unittest { + import std.array; auto w = appender!string(); formattedWrite(w, "%s %d", "@safe/pure", 42); assert(w.data == "@safe/pure 42"); } /** - Reads characters from input range $(D r), converts them according - to $(D fmt), and writes them to $(D args). - - Returns: - - On success, the function returns the number of variables filled. This count - can match the expected number of readings or fewer, even zero, if a - matching failure happens. - - Example: ----- -string s = "hello!124:34.5"; -string a; -int b; -double c; -formattedRead(s, "%s!%s:%s", &a, &b, &c); -assert(a == "hello" && b == 124 && c == 34.5); ----- +Reads characters from input range $(D r), converts them according +to $(D fmt), and writes them to $(D args). + +Params: + r = The range to read from. + fmt = The format of the data to read. + args = The drain of the data read. + +Returns: + +On success, the function returns the number of variables filled. This count +can match the expected number of readings or fewer, even zero, if a +matching failure happens. */ uint formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, S args) { @@ -606,6 +592,17 @@ uint formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, S args) } } +/// +unittest +{ + string s = "hello!124:34.5"; + string a; + int b; + double c; + formattedRead(s, "%s!%s:%s", &a, &b, &c); + assert(a == "hello" && b == 124 && c == 34.5); +} + unittest { import std.math; @@ -615,7 +612,7 @@ unittest assert(s.empty); assert(approxEqual(x, 1.2)); assert(approxEqual(y, 3.4)); - assert(isnan(z)); + assert(isNaN(z)); } template FormatSpec(Char) @@ -627,31 +624,14 @@ template FormatSpec(Char) /** * A General handler for $(D printf) style format specifiers. Used for building more * specific formatting functions. - * - * Example: - * ---- - * auto a = appender!(string)(); - * auto fmt = "Number: %2.4e\nString: %s"; - * auto f = FormatSpec!char(fmt); - * - * f.writeUpToNextSpec(a); - * - * assert(a.data == "Number: "); - * assert(f.trailing == "\nString: %s"); - * assert(f.spec == 'e'); - * assert(f.width == 2); - * assert(f.precision == 4); - * - * f.writeUpToNextSpec(a); - * - * assert(a.data == "Number: \nString: "); - * assert(f.trailing == ""); - * assert(f.spec == 's'); - * ---- */ struct FormatSpec(Char) if (is(Unqual!Char == Char)) { + import std.ascii : isDigit; + import std.algorithm : startsWith; + import std.conv : parse, text, to; + /** Minimum _width, default $(D 0). */ @@ -825,6 +805,7 @@ struct FormatSpec(Char) unittest { + import std.array; auto w = appender!(char[])(); auto f = FormatSpec("abc%sdef%sghi"); f.writeUpToNextSpec(w); @@ -886,7 +867,7 @@ struct FormatSpec(Char) // Get the matching balanced paren for (uint innerParens;;) { - enforce(j < trailing.length, + enforceFmt(j + 1 < trailing.length, text("Incorrect format specifier: %", trailing[i .. $])); if (trailing[j++] != '%') { @@ -894,7 +875,11 @@ struct FormatSpec(Char) continue; } if (trailing[j] == '-') // for %-( + { ++j; // skip + enforceFmt(j < trailing.length, + text("Incorrect format specifier: %", trailing[i .. $])); + } if (trailing[j] == ')') { if (innerParens-- == 0) break; @@ -924,12 +909,12 @@ struct FormatSpec(Char) text("Incorrect format specifier: %", trailing[j .. $])); } - nested = to!(typeof(nested))(trailing[i + 1 .. k - 1]); - sep = to!(typeof(nested))(trailing[k + 1 .. j - 1]); + nested = trailing[i + 1 .. k - 1]; + sep = trailing[k + 1 .. j - 1]; } else { - nested = to!(typeof(nested))(trailing[i + 1 .. j - 1]); + nested = trailing[i + 1 .. j - 1]; sep = null; // use null (issue 12135) } //this = FormatSpec(innerTrailingSpec); @@ -948,7 +933,7 @@ struct FormatSpec(Char) // a '*' followed by digits and '$' is a // positional format trailing = trailing[1 .. $]; - width = -.parse!(typeof(width))(trailing); + width = -parse!(typeof(width))(trailing); i = 0; enforceFmt(trailing[i++] == '$', "$ expected"); @@ -961,7 +946,7 @@ struct FormatSpec(Char) break; case '1': .. case '9': auto tmp = trailing[i .. $]; - const widthOrArgIndex = .parse!uint(tmp); + const widthOrArgIndex = parse!uint(tmp); enforceFmt(tmp.length, text("Incorrect format specifier %", trailing[i .. $])); i = tmp.ptr - trailing.ptr; @@ -971,7 +956,7 @@ struct FormatSpec(Char) indexEnd = indexStart = to!ubyte(widthOrArgIndex); ++i; } - else if (tmp.length && tmp[0] == ':') + else if (tmp.startsWith(':')) { // two indexes of the form %m:n$, or one index of the form %m:$ indexStart = to!ubyte(widthOrArgIndex); @@ -982,7 +967,7 @@ struct FormatSpec(Char) } else { - indexEnd = .parse!(typeof(indexEnd))(tmp); + indexEnd = parse!(typeof(indexEnd))(tmp); } i = tmp.ptr - trailing.ptr; enforceFmt(trailing[i++] == '$', @@ -1004,7 +989,7 @@ struct FormatSpec(Char) // positional precision trailing = trailing[i .. $]; i = 0; - precision = -.parse!int(trailing); + precision = -parse!int(trailing); enforceFmt(trailing[i++] == '$', "$ expected"); } @@ -1019,13 +1004,13 @@ struct FormatSpec(Char) // negative precision, as good as 0 precision = 0; auto tmp = trailing[i .. $]; - .parse!int(tmp); // skip digits + parse!int(tmp); // skip digits i = tmp.ptr - trailing.ptr; } else if (isDigit(trailing[i])) { auto tmp = trailing[i .. $]; - precision = .parse!int(tmp); + precision = parse!int(tmp); i = tmp.ptr - trailing.ptr; } else @@ -1047,6 +1032,8 @@ struct FormatSpec(Char) //-------------------------------------------------------------------------- private bool readUpToNextSpec(R)(ref R r) { + import std.ascii : isLower; + // Reset content if (__ctfe) { @@ -1108,8 +1095,9 @@ struct FormatSpec(Char) return false; } - private const string getCurFmtStr() + private string getCurFmtStr() const { + import std.array : appender; auto w = appender!string(); auto f = FormatSpec!Char("%s"); // for stringnize @@ -1138,6 +1126,7 @@ struct FormatSpec(Char) unittest { // issue 5237 + import std.array; auto w = appender!string(); auto f = FormatSpec!char("%.16f"); f.writeUpToNextSpec(w); // dummy eating @@ -1148,6 +1137,7 @@ struct FormatSpec(Char) private const(Char)[] headUpToNextSpec() { + import std.array : appender; auto w = appender!(typeof(return))(); auto tr = trailing; @@ -1189,9 +1179,11 @@ struct FormatSpec(Char) "\ntrailing = ", trailing, "\n"); } } + +/// @safe pure unittest { - //Test the example + import std.array; auto a = appender!(string)(); auto fmt = "Number: %2.4e\nString: %s"; auto f = FormatSpec!char(fmt); @@ -1211,16 +1203,34 @@ struct FormatSpec(Char) assert(f.spec == 's'); } +// Issue 14059 +unittest +{ + import std.array : appender; + auto a = appender!(string)(); + + auto f = FormatSpec!char("%-(%s%"); + assertThrown(f.writeUpToNextSpec(a)); + + f = FormatSpec!char("%(%-"); + assertThrown(f.writeUpToNextSpec(a)); +} + /** - Helper function that returns a $(D FormatSpec) for a single specifier given - in $(D fmt) +Helper function that returns a $(D FormatSpec) for a single specifier given +in $(D fmt) - Returns a $(D FormatSpec) with the specifier parsed. +Params: + fmt = A format specifier - Enforces giving only one specifier to the function. +Returns: + A $(D FormatSpec) with the specifier parsed. + +Enforces giving only one specifier to the function. */ FormatSpec!Char singleSpec(Char)(Char[] fmt) { + import std.conv : text; enforce(fmt.length >= 2, new Exception("fmt must be at least 2 characters long")); enforce(fmt.front == '%', new Exception("fmt must start with a '%' character")); @@ -1237,6 +1247,8 @@ FormatSpec!Char singleSpec(Char)(Char[] fmt) return spec; } + +/// unittest { auto spec = singleSpec("%2.3e"); @@ -1252,8 +1264,13 @@ unittest } /** - $(D bool)s are formatted as "true" or "false" with %s and as "1" or - "0" with integral-specific format specs. +$(D bool)s are formatted as "true" or "false" with %s and as "1" or +"0" with integral-specific format specs. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) @@ -1282,6 +1299,17 @@ if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) formatValue(w, cast(int) val, f); } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatValue(w, true, spec); + + assert(w.data == "true"); +} + @safe pure unittest { assertCTFEable!( @@ -1319,7 +1347,12 @@ unittest } /** - $(D null) literal is formatted as $(D "null"). +$(D null) literal is formatted as $(D "null"). + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(Unqual!T == typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) @@ -1330,6 +1363,17 @@ if (is(Unqual!T == typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) put(w, "null"); } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatValue(w, null, spec); + + assert(w.data == "null"); +} + @safe pure unittest { assertCTFEable!( @@ -1339,11 +1383,17 @@ if (is(Unqual!T == typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) } /** - Integrals are formatted like $(D printf) does. +Integrals are formatted like $(D printf) does. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) { + import std.system : Endian; alias U = IntegralTypeOf!T; U val = obj; // Extracting alias this may be impure/system/may-throw @@ -1385,6 +1435,17 @@ if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) formatIntegral(w, cast(ulong) val, f, base, U.max); } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%d"); + formatValue(w, 1337, spec); + + assert(w.data == "1337"); +} + private void formatIntegral(Writer, T, Char)(Writer w, const(T) val, ref FormatSpec!Char f, uint base, ulong mask) { FormatSpec!Char fs = f; // fs is copy for change its values. @@ -1567,11 +1628,19 @@ unittest } /** - * Floating-point values are formatted like $(D printf) does. +Floating-point values are formatted like $(D printf) does. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) { + import core.stdc.stdio : snprintf; + import std.system : Endian; + import std.algorithm : find, min; FormatSpec!Char fs = f; // fs is copy for change its values. FloatingPointTypeOf!T val = obj; @@ -1595,14 +1664,15 @@ if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) } return; } - enforceFmt(std.algorithm.find("fgFGaAeEs", fs.spec).length, + enforceFmt(find("fgFGaAeEs", fs.spec).length, "floating"); version (CRuntime_Microsoft) { + import std.math : isNaN, isInfinity; double tval = val; // convert early to get "inf" in case of overflow string s; - if (isnan(tval)) + if (isNaN(tval)) s = "nan"; // snprintf writes 1.#QNAN else if (isInfinity(tval)) s = val >= 0 ? "inf" : "-inf"; // snprintf writes 1.#INF @@ -1668,8 +1738,20 @@ if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) put(w, buf[0 .. min(n, buf.length-1)]); } -@safe /*pure */unittest +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%.1f"); + formatValue(w, 1337.7, spec); + + assert(w.data == "1337.7"); +} + +@safe /*pure*/ unittest // formatting floating point values is now impure { + import std.conv : to; foreach (T; TypeTuple!(float, double, real)) { formatTest( to!( T)(5.5), "5.5" ); @@ -1700,7 +1782,12 @@ unittest } /* - Formatting a $(D creal) is deprecated but still kept around for a while. +Formatting a $(D creal) is deprecated but still kept around for a while. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(Unqual!T : creal) && !is(T == enum) && !hasToString!(T, Char)) @@ -1716,8 +1803,9 @@ if (is(Unqual!T : creal) && !is(T == enum) && !hasToString!(T, Char)) put(w, 'i'); } -/*@safe pure */unittest +@safe /*pure*/ unittest // formatting floating point values is now impure { + import std.conv : to; foreach (T; TypeTuple!(cfloat, cdouble, creal)) { formatTest( to!( T)(1 + 1i), "1+1i" ); @@ -1751,6 +1839,11 @@ unittest /* Formatting an $(D ireal) is deprecated but still kept around for a while. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(Unqual!T : ireal) && !is(T == enum) && !hasToString!(T, Char)) @@ -1761,8 +1854,9 @@ if (is(Unqual!T : ireal) && !is(T == enum) && !hasToString!(T, Char)) put(w, 'i'); } -/*@safe pure */unittest +@safe /*pure*/ unittest // formatting floating point values is now impure { + import std.conv : to; foreach (T; TypeTuple!(ifloat, idouble, ireal)) { formatTest( to!( T)(1i), "1i" ); @@ -1789,9 +1883,14 @@ unittest } /** - Individual characters ($(D char), $(D wchar), or $(D dchar)) are - formatted as Unicode characters with %s and as integers with - integral-specific format specs. +Individual characters ($(D char), $(D wchar), or $(D dchar)) are formatted as +Unicode characters with %s and as integers with integral-specific format +specs. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) @@ -1809,6 +1908,17 @@ if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) } } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%c"); + formatValue(w, 'a', spec); + + assert(w.data == "a"); +} + @safe pure unittest { assertCTFEable!( @@ -1848,7 +1958,12 @@ unittest } /** - Strings are formatted like $(D printf) does. +Strings are formatted like $(D printf) does. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) @@ -1857,6 +1972,17 @@ if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToSt formatRange(w, val, f); } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatValue(w, "hello", spec); + + assert(w.data == "hello"); +} + unittest { formatTest( "abc", "abc" ); @@ -1908,7 +2034,12 @@ unittest } /** - Static-size arrays are formatted as dynamic arrays. +Static-size arrays are formatted as dynamic arrays. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, auto ref T obj, ref FormatSpec!Char f) if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) @@ -1916,8 +2047,21 @@ if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) formatValue(w, obj[], f); } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + char[2] two = ['a', 'b']; + formatValue(w, two, spec); + + assert(w.data == "ab"); +} + unittest // Test for issue 8310 { + import std.array : appender; FormatSpec!char f; auto w = appender!string(); @@ -1929,11 +2073,16 @@ unittest // Test for issue 8310 } /** - Dynamic arrays are formatted as input ranges. +Dynamic arrays are formatted as input ranges. + +Specializations: + $(UL $(LI $(D void[]) is formatted like $(D ubyte[]).) + $(LI Const array is converted to input range by removing its qualifier.)) - Specializations: - $(UL $(LI $(D void[]) is formatted like $(D ubyte[]).) - $(LI Const array is converted to input range by removing its qualifier.)) +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) @@ -1955,6 +2104,18 @@ if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToS } } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + auto two = [1, 2]; + formatValue(w, two, spec); + + assert(w.data == "[1, 2]"); +} + // alias this, input range I/F, and toString() unittest { @@ -2044,11 +2205,11 @@ unittest struct Range { string value; - const @property bool empty(){ return !value.length; } - const @property dchar front(){ return value.front; } - void popFront(){ value.popFront(); } + @property bool empty() const { return !value.length; } + @property dchar front() const { return value.front; } + void popFront() { value.popFront(); } - const @property size_t length(){ return value.length; } + @property size_t length() const { return value.length; } } immutable table = [ @@ -2139,6 +2300,8 @@ unittest private void formatRange(Writer, T, Char)(ref Writer w, ref T val, ref FormatSpec!Char f) if (isInputRange!T) { + import std.conv : text; + // Formatting character ranges like string if (f.spec == 's') { @@ -2335,11 +2498,14 @@ private void formatChar(Writer)(Writer w, in dchar c, in char quote) formattedWrite(w, "\\U%08X", cast(uint)c); } -// undocumented +// undocumented because of deprecation // string elements are formatted like UTF-8 string literals. void formatElement(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f) if (is(StringTypeOf!T) && !is(T == enum)) { + import std.utf : UTFException; + import std.array : appender; + StringTypeOf!T str = val; // bug 8015 if (f.spec == 's') @@ -2389,6 +2555,16 @@ if (is(StringTypeOf!T) && !is(T == enum)) formatValue(w, str, f); } +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, "Hello World", spec); + + assert(w.data == "\"Hello World\""); +} + unittest { // Test for bug 8015 @@ -2405,8 +2581,8 @@ unittest Tuple!(MyStruct) t; } -// undocumented -// character elements are formatted like UTF-8 character literals. +// undocumented because of deprecation +// Character elements are formatted like UTF-8 character literals. void formatElement(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f) if (is(CharTypeOf!T) && !is(T == enum)) { @@ -2420,6 +2596,17 @@ if (is(CharTypeOf!T) && !is(T == enum)) formatValue(w, val, f); } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, "H", spec); + + assert(w.data == "\"H\"", w.data); +} + // undocumented // Maybe T is noncopyable struct, so receive it by 'auto ref'. void formatElement(Writer, T, Char)(Writer w, auto ref T val, ref FormatSpec!Char f) @@ -2431,6 +2618,11 @@ if (!is(StringTypeOf!T) && !is(CharTypeOf!T) || is(T == enum)) /** Associative arrays are formatted by using $(D ':') and $(D ", ") as separators, and enclosed by $(D '[') and $(D ']'). + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T obj, ref FormatSpec!Char f) if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) @@ -2479,6 +2671,18 @@ if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) put(w, f.seqAfter); } +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + auto aa = ["H":"W"]; + formatElement(w, aa, spec); + + assert(w.data == "[\"H\":\"W\"]", w.data); +} + unittest { int[string] aa0; @@ -2709,7 +2913,6 @@ if (is(T == class) && !is(T == enum)) unittest { import std.format; - import std.string : format; struct Point { @@ -2751,6 +2954,8 @@ unittest unittest { + import std.array : appender; + import std.range.interfaces; // class range (issue 5154) auto c = inputRangeObject([1,2,3,4]); formatTest( c, "[1, 2, 3, 4]" ); @@ -2830,7 +3035,7 @@ if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is { version (Windows) { - import std.c.windows.com : IUnknown; + import core.sys.windows.com : IUnknown; static if (is(T : IUnknown)) { formatValue(w, *cast(void**)&val, f); @@ -2851,6 +3056,7 @@ if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is unittest { // interface + import std.range.interfaces; InputRange!int i = inputRangeObject([1,2,3,4]); formatTest( i, "[1, 2, 3, 4]" ); assert(i.empty); @@ -2870,7 +3076,7 @@ unittest version (Windows) { import core.sys.windows.windows : HRESULT; - import std.c.windows.com : IUnknown, IID; + import core.sys.windows.com : IUnknown, IID; interface IUnknown2 : IUnknown { } @@ -2982,6 +3188,7 @@ unittest unittest { + import std.array; // 7230 static struct Bug7230 { @@ -3005,6 +3212,7 @@ unittest unittest { + import std.array; static struct S{ @disable this(this); } S s; @@ -3015,7 +3223,12 @@ unittest } /** - $(D enum) is formatted like its base value. +$(D enum) is formatted like its base value. + +Params: + w = The $(D OutputRange) to write to. + val = The value to write. + f = The $(D FormatSpec) defining how to write the value. */ void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f) if (is(T == enum)) @@ -3037,6 +3250,21 @@ if (is(T == enum)) } formatValue(w, cast(OriginalType!T)val, f); } + +/// +unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + + enum A { first, second, third } + + formatElement(w, A.second, spec); + + assert(w.data == "second"); +} + unittest { enum A { first, second, third } @@ -3113,6 +3341,7 @@ if (isPointer!T && !is(T == enum) && !hasToString!(T, Char)) @safe pure unittest { // pointer + import std.range; auto r = retro([1,2,3,4]); auto p = ()@trusted{ auto p = &r; return p; }(); formatTest( p, "[4, 3, 2, 1]" ); @@ -3124,7 +3353,7 @@ if (isPointer!T && !is(T == enum) && !hasToString!(T, Char)) formatTest( q, "FFEECCAA" ); } -unittest +pure unittest { // Test for issue 7869 struct S @@ -3150,14 +3379,14 @@ unittest formatTest( B.init, "null" ); } -unittest +pure unittest { // Test for issue 9336 shared int i; format("%s", &i); } -unittest +pure unittest { // Test for issue 11778 int* p = null; @@ -3165,7 +3394,7 @@ unittest assertThrown(format("%04d", p + 2)); } -@safe pure unittest +pure unittest { // Test for issue 12505 void* p = null; @@ -3173,39 +3402,38 @@ unittest } /** - Delegates are formatted by 'Attributes ReturnType delegate(Parameters)' + Delegates are formatted by 'ReturnType delegate(Parameters) FunctionAttributes' */ -void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f) -if (is(T == delegate) && !is(T == enum) && !hasToString!(T, Char)) +void formatValue(Writer, T, Char)(Writer w, scope T, ref FormatSpec!Char f) + if (isDelegate!T) { - alias FA = FunctionAttribute; - if (functionAttributes!T & FA.pure_) formatValue(w, "pure ", f); - if (functionAttributes!T & FA.nothrow_) formatValue(w, "nothrow ", f); - if (functionAttributes!T & FA.ref_) formatValue(w, "ref ", f); - if (functionAttributes!T & FA.property) formatValue(w, "@property ", f); - if (functionAttributes!T & FA.trusted) formatValue(w, "@trusted ", f); - if (functionAttributes!T & FA.safe) formatValue(w, "@safe ", f); - formatValue(w, ReturnType!T.stringof, f); - formatValue(w, " delegate", f); - formatValue(w, ParameterTypeTuple!T.stringof, f); + formatValue(w, T.stringof, f); } +/// unittest { - void func() {} - formatTest( &func, "void delegate()" ); -} + import std.conv : to; -/* - Formatting a $(D typedef) is deprecated but still kept around for a while. - */ -void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f) -if (is(T == typedef)) -{ - static if (is(T U == typedef)) + int i; + + int foo(short k) @nogc { - formatValue(w, cast(U) val, f); + return i + k; + } + + int delegate(short) @nogc bar() nothrow + { + return &foo; } + + assert(to!string(&bar) == "int delegate(short) @nogc delegate() nothrow"); +} + +unittest +{ + void func() {} + formatTest( &func, "void delegate()" ); } /* @@ -3221,6 +3449,7 @@ private void formatGeneric(Writer, D, Char)(Writer w, const(void)* arg, ref Form private void formatNth(Writer, Char, A...)(Writer w, ref FormatSpec!Char f, size_t index, A args) { + import std.conv : to; static string gencode(size_t count)() { string result; @@ -3244,7 +3473,7 @@ private void formatNth(Writer, Char, A...)(Writer w, ref FormatSpec!Char f, size } } -unittest +pure unittest { int[] a = [ 1, 3, 2 ]; formatTest( "testing %(%s & %) embedded", a, @@ -3261,6 +3490,7 @@ unittest // Fix for issue 1591 private int getNthInt(A...)(uint index, A args) { + import std.conv : to; static if (A.length) { if (index) @@ -3287,6 +3517,9 @@ private int getNthInt(A...)(uint index, A args) version(unittest) void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__) { + import core.exception; + import std.array : appender; + import std.conv : text; FormatSpec!char f; auto w = appender!string(); formatValue(w, val, f); @@ -3298,6 +3531,9 @@ void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __F version(unittest) void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) { + import core.exception; + import std.array : appender; + import std.conv : text; auto w = appender!string(); formattedWrite(w, fmt, val); enforce!AssertError( @@ -3308,6 +3544,9 @@ void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, str version(unittest) void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) { + import core.exception; + import std.conv : text; + import std.array : appender; FormatSpec!char f; auto w = appender!string(); formatValue(w, val, f); @@ -3323,6 +3562,9 @@ void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = _ version(unittest) void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) { + import core.exception; + import std.conv : text; + import std.array : appender; auto w = appender!string(); formattedWrite(w, fmt, val); foreach(cur; expected) @@ -3334,13 +3576,21 @@ void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, s text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); } -unittest +@safe /*pure*/ unittest // formatting floating point values is now impure { + import std.array; + auto stream = appender!string(); formattedWrite(stream, "%s", 1.1); assert(stream.data == "1.1", stream.data); +} - stream = appender!string(); +pure unittest +{ + import std.algorithm; + import std.array; + + auto stream = appender!string(); formattedWrite(stream, "%s", map!"a*a"([2, 3, 5])); assert(stream.data == "[4, 9, 25]", stream.data); @@ -3351,16 +3601,18 @@ unittest assert(stream.data == "6"); } -unittest +pure unittest { + import std.array; auto stream = appender!string(); formattedWrite(stream, "%u", 42); assert(stream.data == "42", stream.data); } -unittest +pure unittest { // testing raw writes + import std.array; auto w = appender!(char[])(); uint a = 0x02030405; formattedWrite(w, "%+r", a); @@ -3372,9 +3624,10 @@ unittest && w.data[2] == 3 && w.data[3] == 2); } -unittest +pure unittest { // testing positional parameters + import std.array; auto w = appender!(char[])(); formattedWrite(w, "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated", @@ -3391,6 +3644,9 @@ unittest unittest { + import std.conv : text, octal; + import std.array; + debug(format) printf("std.format.format.unittest\n"); auto stream = appender!(char[])(); @@ -3403,7 +3659,7 @@ unittest stream.clear(); formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan); - // std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); + // core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); /* The host C library is used to format floats. C99 doesn't * specify what the hex digit before the decimal point is for @@ -3548,7 +3804,7 @@ unittest assert(stream.data == "0.00001000"); //return; - //std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); + //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); s = "helloworld"; string r; @@ -3637,12 +3893,6 @@ unittest //writeln(stream.data); assert(stream.data == "7"); -// assert(false); -// typedef int myint; -// myint m = -7; -// stream.clear(); formattedWrite(stream, "", m); -// assert(stream.data == "-7"); - stream.clear(); formattedWrite(stream, "%s", "abc"c); assert(stream.data == "abc"); stream.clear(); formattedWrite(stream, "%s", "def"w); @@ -3739,7 +3989,7 @@ here: //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); //stream.clear(); formattedWrite(stream, "%s", aa.values); - //std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); + //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]"); //stream.clear(); formattedWrite(stream, "%s", aa); //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); @@ -3864,6 +4114,9 @@ here: unittest { + import std.array; + import std.stdio; + immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); if (false) writeln(aa.keys); assert(aa[3] == "hello"); @@ -3895,11 +4148,11 @@ unittest assert(stream.data == "1"); } - //auto r = std.string.format("%s", aa.values); + //auto r = format("%s", aa.values); stream.clear(); formattedWrite(stream, "%s", aa); //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]", stream.data); -// r = std.string.format("%s", aa); -// assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); + //r = format("%s", aa); + //assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); } unittest @@ -3915,6 +4168,8 @@ unittest version(unittest) void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__) { + import core.exception; + import std.array : appender; auto w = appender!string(); formattedWrite(w, fmt, val); @@ -3955,6 +4210,8 @@ void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = _ version(unittest) void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__) { + import core.exception; + import std.array : appender; auto w = appender!string(); formattedWrite(w, fmt, val); @@ -4098,6 +4355,9 @@ unittest //------------------------------------------------------------------------------ private void skipData(Range, Char)(ref Range input, ref FormatSpec!Char spec) { + import std.ascii : isDigit; + import std.conv : text; + switch (spec.spec) { case 'c': input.popFront(); break; @@ -4127,17 +4387,19 @@ private template acceptedSpecs(T) T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range && is(Unqual!T == bool)) { + import std.algorithm : find; + import std.conv : parse, text; if (spec.spec == 's') { return parse!T(input); } - enforce(std.algorithm.find(acceptedSpecs!long, spec.spec).length, + enforce(find(acceptedSpecs!long, spec.spec).length, text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); return unformatValue!long(input, spec) != 0; } -unittest +pure unittest { string line; @@ -4182,6 +4444,7 @@ unittest T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range && is(T == typeof(null))) { + import std.conv : parse, text; enforce(spec.spec == 's', text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); @@ -4194,7 +4457,9 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range && isIntegral!T && !is(T == enum)) { - enforce(std.algorithm.find(acceptedSpecs!T, spec.spec).length, + import std.algorithm : find; + import std.conv : parse, text; + enforce(find(acceptedSpecs!T, spec.spec).length, text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); enforce(spec.width == 0); // TODO @@ -4214,6 +4479,8 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isFloatingPoint!T && !is(T == enum)) { + import std.algorithm : find; + import std.conv : parse, text; if (spec.spec == 'r') { // raw read @@ -4241,7 +4508,7 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) } return x.typed; } - enforce(std.algorithm.find(acceptedSpecs!T, spec.spec).length, + enforce(find(acceptedSpecs!T, spec.spec).length, text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); return parse!T(input); @@ -4262,8 +4529,9 @@ version(none)unittest assert(witness == a.typed); } -unittest +pure unittest { + import std.typecons; char[] line = "1 2".dup; int a, b; formattedRead(line, "%s %s", &a, &b); @@ -4290,13 +4558,15 @@ unittest T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range && isSomeChar!T && !is(T == enum)) { + import std.algorithm : find; + import std.conv : to, text; if (spec.spec == 's' || spec.spec == 'c') { auto result = to!T(input.front); input.popFront(); return result; } - enforce(std.algorithm.find(acceptedSpecs!T, spec.spec).length, + enforce(find(acceptedSpecs!T, spec.spec).length, text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); static if (T.sizeof == 1) @@ -4309,7 +4579,7 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) static assert(0); } -unittest +pure unittest { string line; @@ -4327,6 +4597,8 @@ unittest T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) { + import std.conv : text; + if (spec.spec == '(') { return unformatRange!T(input, spec); @@ -4340,7 +4612,10 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) auto app = result[]; } else + { + import std.array : appender; auto app = appender!T(); + } if (spec.trailing.empty) { for (; !input.empty; input.popFront()) @@ -4371,7 +4646,7 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) return app.data; } -unittest +pure unittest { string line; @@ -4403,6 +4678,7 @@ unittest T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) { + import std.conv : parse, text; if (spec.spec == '(') { return unformatRange!T(input, spec); @@ -4413,7 +4689,7 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) return parse!T(input); } -unittest +pure unittest { string line; @@ -4423,7 +4699,7 @@ unittest assert(s1 == [1,2,3]); } -unittest +pure unittest { string line; @@ -4448,7 +4724,7 @@ unittest assert(s4 == ["hello", "world"]); } -unittest +pure unittest { string line; @@ -4466,7 +4742,7 @@ unittest assertThrown(formattedRead(line, "%s", &sa3)); } -unittest +pure unittest { string input; @@ -4480,7 +4756,7 @@ unittest assertThrown(formattedRead(input, "[%(%s,%)]", &sa2)); } -unittest +pure unittest { // 7241 string input = "a"; @@ -4496,6 +4772,7 @@ unittest T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range && isAssociativeArray!T && !is(T == enum)) { + import std.conv : parse, text; if (spec.spec == '(') { return unformatRange!T(input, spec); @@ -4506,7 +4783,7 @@ T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) return parse!T(input); } -unittest +pure unittest { string line; @@ -4596,9 +4873,9 @@ body enforce(i <= T.length); } - if (spec.sep) + if (spec.sep != null) fmt.readUpToNextSpec(input); - auto sep = spec.sep ? spec.sep + auto sep = spec.sep != null ? spec.sep : fmt.trailing; debug (unformatRange) { if (!sep.empty && !input.empty) printf("-> %c, sep = %.*s\n", input.front, sep); @@ -4632,6 +4909,7 @@ body T unformatElement(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) if (isInputRange!Range) { + import std.conv : parseElement; static if (isSomeString!T) { if (spec.spec == 's') @@ -4995,8 +5273,54 @@ void main() } ------------------------ */ -void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) -{ +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; @@ -5152,7 +5476,8 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) int n; version (CRuntime_Microsoft) { - if (isnan(v)) // snprintf writes 1.#QNAN + 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"); @@ -5207,33 +5532,9 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) while (len--) { //doFormat(putc, (&valti)[0 .. 1], p); - version (Win64) - { - void* q = void; - - if (tsize > 8 && m != Mangle.Tsarray) - { q = p; - argptr = cast(va_list)&q; - } - else - argptr = cast(va_list)p; - formatArg('s'); - p += tsize; - } - else - { - version (X86) - argptr = cast(va_list)p; - else version(X86_64) - { __va_list va; - va.stack_args = p; - argptr = &va; - } - else - static assert(false, "unsupported platform"); - formatArg('s'); - p += tsize; - } + argptr = p; + formatArg('s'); + p += tsize; if (len > 0) putc(','); } m = mSave; @@ -5272,24 +5573,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) //doFormat(putc, (&keyti)[0..1], pkey); m = getMan(keyti); - version (X86) - argptr = cast(va_list)pkey; - else version (Win64) - { - void* q = void; - if (keysize > 8 && m != Mangle.Tsarray) - { q = pkey; - argptr = cast(va_list)&q; - } - else - argptr = cast(va_list)pkey; - } - else version (X86_64) - { __va_list va; - va.stack_args = pkey; - argptr = &va; - } - else static assert(false, "unsupported platform"); + argptr = pkey; ti = keyti; formatArg('s'); @@ -5297,25 +5581,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) putc(':'); //doFormat(putc, (&valti)[0..1], pvalue); m = getMan(valti); - version (X86) - argptr = cast(va_list)pvalue; - else version (Win64) - { - void* q2 = void; - auto valuesize = valti.tsize; - if (valuesize > 8 && m != Mangle.Tsarray) - { q2 = pvalue; - argptr = cast(va_list)&q2; - } - else - argptr = cast(va_list)pvalue; - } - else version (X86_64) - { __va_list va2; - va2.stack_args = pvalue; - argptr = &va2; - } - else static assert(false, "unsupported platform"); + argptr = pvalue; ti = valti; formatArg('s'); @@ -5331,7 +5597,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) switch (m) { case Mangle.Tbool: - vbit = va_arg!(bool)(argptr); + vbit = getArg!(bool)(); if (fc != 's') { vnumber = vbit; goto Lnumber; @@ -5340,7 +5606,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) return; case Mangle.Tchar: - vchar = va_arg!(char)(argptr); + vchar = getArg!(char)(); if (fc != 's') { vnumber = vchar; goto Lnumber; @@ -5350,11 +5616,11 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) return; case Mangle.Twchar: - vdchar = va_arg!(wchar)(argptr); + vdchar = getArg!(wchar)(); goto L1; case Mangle.Tdchar: - vdchar = va_arg!(dchar)(argptr); + vdchar = getArg!(dchar)(); L1: if (fc != 's') { vnumber = vdchar; @@ -5374,44 +5640,44 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) case Mangle.Tbyte: signed = 1; - vnumber = va_arg!(byte)(argptr); + vnumber = getArg!(byte)(); goto Lnumber; case Mangle.Tubyte: - vnumber = va_arg!(ubyte)(argptr); + vnumber = getArg!(ubyte)(); goto Lnumber; case Mangle.Tshort: signed = 1; - vnumber = va_arg!(short)(argptr); + vnumber = getArg!(short)(); goto Lnumber; case Mangle.Tushort: - vnumber = va_arg!(ushort)(argptr); + vnumber = getArg!(ushort)(); goto Lnumber; case Mangle.Tint: signed = 1; - vnumber = va_arg!(int)(argptr); + vnumber = getArg!(int)(); goto Lnumber; case Mangle.Tuint: Luint: - vnumber = va_arg!(uint)(argptr); + vnumber = getArg!(uint)(); goto Lnumber; case Mangle.Tlong: signed = 1; - vnumber = cast(ulong)va_arg!(long)(argptr); + vnumber = cast(ulong)getArg!(long)(); goto Lnumber; case Mangle.Tulong: Lulong: - vnumber = va_arg!(ulong)(argptr); + vnumber = getArg!(ulong)(); goto Lnumber; case Mangle.Tclass: - vobject = va_arg!(Object)(argptr); + vobject = getArg!(Object)(); if (vobject is null) s = "null"; else @@ -5419,7 +5685,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) goto Lputstr; case Mangle.Tpointer: - vnumber = cast(ulong)va_arg!(void*)(argptr); + vnumber = cast(ulong)getArg!(void*)(); if (fc != 'x') uc = 1; flags |= FL0pad; if (!(flags & FLprecision)) @@ -5433,40 +5699,35 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) case Mangle.Tifloat: if (fc == 'x' || fc == 'X') goto Luint; - vreal = va_arg!(float)(argptr); + vreal = getArg!(float)(); goto Lreal; case Mangle.Tdouble: case Mangle.Tidouble: if (fc == 'x' || fc == 'X') goto Lulong; - vreal = va_arg!(double)(argptr); + vreal = getArg!(double)(); goto Lreal; case Mangle.Treal: case Mangle.Tireal: - vreal = va_arg!(real)(argptr); + vreal = getArg!(real)(); goto Lreal; case Mangle.Tcfloat: - vcreal = va_arg!(cfloat)(argptr); + vcreal = getArg!(cfloat)(); goto Lcomplex; case Mangle.Tcdouble: - vcreal = va_arg!(cdouble)(argptr); + vcreal = getArg!(cdouble)(); goto Lcomplex; case Mangle.Tcreal: - vcreal = va_arg!(creal)(argptr); + vcreal = getArg!(creal)(); goto Lcomplex; case Mangle.Tsarray: - version (X86) - putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); - else version (Win64) - putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); - else - putArray((cast(__va_list*)argptr).stack_args, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); + putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); return; case Mangle.Tarray: @@ -5484,14 +5745,14 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) default: break; } - void[] va = va_arg!(void[])(argptr); + 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 = va_arg!(ubyte[long])(argptr); + ubyte[long] vaa = getArg!(ubyte[long])(); putAArray(vaa, (cast(TypeInfo_AssociativeArray)ti).next, (cast(TypeInfo_AssociativeArray)ti).key); @@ -5505,18 +5766,18 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) { case Mangle.Tchar: LarrayChar: - s = va_arg!(string)(argptr); + s = getArg!(string)(); goto Lputstr; case Mangle.Twchar: LarrayWchar: - wchar[] sw = va_arg!(wchar[])(argptr); + wchar[] sw = getArg!(wchar[])(); s = toUTF8(sw); goto Lputstr; case Mangle.Tdchar: LarrayDchar: - s = toUTF8(va_arg!(dstring)(argptr)); + s = toUTF8(getArg!(dstring)()); Lputstr: if (fc != 's') throw new FormatException("string"); @@ -5534,7 +5795,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) TypeInfo ti2 = primitiveTypeInfo(m2); if (!ti2) goto Lerror; - void[] va = va_arg!(void[])(argptr); + void[] va = getArg!(void[])(); putArray(va.ptr, va.length, ti2); } return; @@ -5558,44 +5819,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) if (tis.xtoString is null) throw new FormatException("Can't convert " ~ tis.toString() ~ " to string: \"string toString()\" not defined"); - version(X86) - { - s = tis.xtoString(argptr); - argptr += (tis.tsize + 3) & ~3; - } - else version(Win64) - { - void* p = argptr; - if (tis.tsize > 8) - p = *cast(void**)p; - s = tis.xtoString(p); - argptr += size_t.sizeof; - } - else version (X86_64) - { - void[32] parmn = void; // place to copy struct if passed in regs - void* p; - auto tsize = tis.tsize; - TypeInfo arg1, arg2; - if (!tis.argTypes(arg1, arg2)) // if could be passed in regs - { assert(tsize <= parmn.length); - p = parmn.ptr; - va_arg(argptr, tis, p); - } - else - { /* Avoid making a copy of the struct; take advantage of - * it always being passed in memory - */ - // The arg may have more strict alignment than the stack - auto talign = tis.talign; - __va_list* ap = cast(__va_list*)argptr; - p = cast(void*)((cast(size_t)ap.stack_args + talign - 1) & ~(talign - 1)); - ap.stack_args = cast(void*)(cast(size_t)p + ((tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1))); - } - s = tis.xtoString(p); - } - else - static assert(0); + s = tis.xtoString(skipArg(tis)); goto Lputstr; } @@ -5787,16 +6011,16 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) switch (m2) { case Mangle.Tchar: - fmt = va_arg!(string)(argptr); + fmt = getArg!(string)(); break; case Mangle.Twchar: - wfmt = va_arg!(wstring)(argptr); + wfmt = getArg!(wstring)(); fmt = toUTF8(wfmt); break; case Mangle.Tdchar: - dfmt = va_arg!(dstring)(argptr); + dfmt = getArg!(dstring)(); fmt = toUTF8(dfmt); break; @@ -5845,7 +6069,7 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) m = cast(Mangle)typeid(ti).name[9]; if (m != Mangle.Tint) throw new FormatException("int argument expected"); - return va_arg!(int)(argptr); + return getArg!(int)(); } if (c != '%') @@ -5953,15 +6177,17 @@ void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) unittest { + import std.conv : octal; + int i; string s; debug(format) printf("std.format.format.unittest\n"); - s = std.string.format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); + 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 = std.string.format("%s %A %s", 1.67, -1.28, float.nan); + 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. @@ -5985,13 +6211,13 @@ unittest else assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); - s = std.string.format("%x %X", 0x1234AF, 0xAFAFAFAF); + s = format("%x %X", 0x1234AF, 0xAFAFAFAF); assert(s == "1234af AFAFAFAF"); - s = std.string.format("%b %o", 0x1234AF, 0xAFAFAFAF); + s = format("%b %o", 0x1234AF, 0xAFAFAFAF); assert(s == "100100011010010101111 25753727657"); - s = std.string.format("%d %s", 0x1234AF, 0xAFAFAFAF); + s = format("%d %s", 0x1234AF, 0xAFAFAFAF); assert(s == "1193135 2947526575"); //version(X86_64) @@ -6000,268 +6226,264 @@ unittest //} //else //{ - s = std.string.format("%s", 1.2 + 3.4i); + s = format("%s", 1.2 + 3.4i); assert(s == "1.2+3.4i", s); - //s = std.string.format("%x %X", 1.32, 6.78f); + //s = format("%x %X", 1.32, 6.78f); //assert(s == "3ff51eb851eb851f 40D8F5C3"); //} - s = std.string.format("%#06.*f",2,12.345); + s = format("%#06.*f",2,12.345); assert(s == "012.35"); - s = std.string.format("%#0*.*f",6,2,12.345); + s = format("%#0*.*f",6,2,12.345); assert(s == "012.35"); - s = std.string.format("%7.4g:", 12.678); + s = format("%7.4g:", 12.678); assert(s == " 12.68:"); - s = std.string.format("%7.4g:", 12.678L); + s = format("%7.4g:", 12.678L); assert(s == " 12.68:"); - s = std.string.format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); + s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); assert(s == "-4.000000|-0010|0x001| 0x1"); i = -10; - s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + 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 = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + 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 = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + 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 = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + 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 = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); assert(s == "10| 10|010|10|10.0000"); - s = std.string.format("%.0d", 0); + s = format("%.0d", 0); assert(s == ""); - s = std.string.format("%.g", .34); + s = format("%.g", .34); assert(s == "0.3"); - s = std.string.format("%.0g", .34); + s = format("%.0g", .34); assert(s == "0.3"); - s = std.string.format("%.2g", .34); + s = format("%.2g", .34); assert(s == "0.34"); - s = std.string.format("%0.0008f", 1e-08); + s = format("%0.0008f", 1e-08); assert(s == "0.00000001"); - s = std.string.format("%0.0008f", 1e-05); + s = format("%0.0008f", 1e-05); assert(s == "0.00001000"); s = "helloworld"; string r; - r = std.string.format("%.2s", s[0..5]); + r = format("%.2s", s[0..5]); assert(r == "he"); - r = std.string.format("%.20s", s[0..5]); + r = format("%.20s", s[0..5]); assert(r == "hello"); - r = std.string.format("%8s", s[0..5]); + r = format("%8s", s[0..5]); assert(r == " hello"); byte[] arrbyte = new byte[4]; arrbyte[0] = 100; arrbyte[1] = -99; arrbyte[3] = 0; - r = std.string.format("%s", arrbyte); + 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 = std.string.format("%s", arrubyte); + 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 = std.string.format("%s", arrshort); + 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 = std.string.format("%s", arrushort); + 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 = std.string.format("%s", arrint); + 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 = std.string.format("%s", arrlong); + 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 = std.string.format("%s", arrulong); + 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 = std.string.format("%s", arr2); + r = format("%s", arr2); assert(r == `["hello", "world", "", "foo"]`); - r = std.string.format("%.8d", 7); + r = format("%.8d", 7); assert(r == "00000007"); - r = std.string.format("%.8x", 10); + r = format("%.8x", 10); assert(r == "0000000a"); - r = std.string.format("%-3d", 7); + r = format("%-3d", 7); assert(r == "7 "); - r = std.string.format("%*d", -3, 7); + r = format("%*d", -3, 7); assert(r == "7 "); - r = std.string.format("%.*d", -3, 7); + r = format("%.*d", -3, 7); assert(r == "7"); - //typedef int myint; - //myint m = -7; - //r = std.string.format(m); - //assert(r == "-7"); - - r = std.string.format("abc"c); + r = format("abc"c); assert(r == "abc"); - r = std.string.format("def"w); + r = format("def"w); assert(r == "def"); - r = std.string.format("ghi"d); + r = format("ghi"d); assert(r == "ghi"); void* p = cast(void*)0xDEADBEEF; - r = std.string.format("%s", p); + r = format("%s", p); assert(r == "DEADBEEF"); - r = std.string.format("%#x", 0xabcd); + r = format("%#x", 0xabcd); assert(r == "0xabcd"); - r = std.string.format("%#X", 0xABCD); + r = format("%#X", 0xABCD); assert(r == "0XABCD"); - r = std.string.format("%#o", octal!12345); + r = format("%#o", octal!12345); assert(r == "012345"); - r = std.string.format("%o", 9); + r = format("%o", 9); assert(r == "11"); - r = std.string.format("%+d", 123); + r = format("%+d", 123); assert(r == "+123"); - r = std.string.format("%+d", -123); + r = format("%+d", -123); assert(r == "-123"); - r = std.string.format("% d", 123); + r = format("% d", 123); assert(r == " 123"); - r = std.string.format("% d", -123); + r = format("% d", -123); assert(r == "-123"); - r = std.string.format("%%"); + r = format("%%"); assert(r == "%"); - r = std.string.format("%d", true); + r = format("%d", true); assert(r == "1"); - r = std.string.format("%d", false); + r = format("%d", false); assert(r == "0"); - r = std.string.format("%d", 'a'); + r = format("%d", 'a'); assert(r == "97"); wchar wc = 'a'; - r = std.string.format("%d", wc); + r = format("%d", wc); assert(r == "97"); dchar dc = 'a'; - r = std.string.format("%d", dc); + r = format("%d", dc); assert(r == "97"); byte b = byte.max; - r = std.string.format("%x", b); + r = format("%x", b); assert(r == "7f"); - r = std.string.format("%x", ++b); + r = format("%x", ++b); assert(r == "80"); - r = std.string.format("%x", ++b); + r = format("%x", ++b); assert(r == "81"); short sh = short.max; - r = std.string.format("%x", sh); + r = format("%x", sh); assert(r == "7fff"); - r = std.string.format("%x", ++sh); + r = format("%x", ++sh); assert(r == "8000"); - r = std.string.format("%x", ++sh); + r = format("%x", ++sh); assert(r == "8001"); i = int.max; - r = std.string.format("%x", i); + r = format("%x", i); assert(r == "7fffffff"); - r = std.string.format("%x", ++i); + r = format("%x", ++i); assert(r == "80000000"); - r = std.string.format("%x", ++i); + r = format("%x", ++i); assert(r == "80000001"); - r = std.string.format("%x", 10); + r = format("%x", 10); assert(r == "a"); - r = std.string.format("%X", 10); + r = format("%X", 10); assert(r == "A"); - r = std.string.format("%x", 15); + r = format("%x", 15); assert(r == "f"); - r = std.string.format("%X", 15); + r = format("%X", 15); assert(r == "F"); Object c = null; - r = std.string.format("%s", c); + r = format("%s", c); assert(r == "null"); enum TestEnum { Value1, Value2 } - r = std.string.format("%s", TestEnum.Value2); + r = format("%s", TestEnum.Value2); assert(r == "Value2"); immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); - r = std.string.format("%s", aa.values); + r = format("%s", aa.values); assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); - r = std.string.format("%s", aa); + 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 = std.string.format(" %d", ds[j]); + r = format(" %d", ds[j]); if (j == 0) assert(r == " 97"); else assert(r == " 98"); } - r = std.string.format(">%14d<, %s", 15, [1,2,3]); + r = format(">%14d<, %s", 15, [1,2,3]); assert(r == "> 15<, [1, 2, 3]"); - assert(std.string.format("%8s", "bar") == " bar"); - assert(std.string.format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); + assert(format("%8s", "bar") == " bar"); + assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); } unittest { // bugzilla 3479 + import std.array; auto stream = appender!(char[])(); formattedWrite(stream, "%2$.*1$d", 12, 10); assert(stream.data == "000000000010", stream.data); @@ -6270,8 +6492,159 @@ unittest unittest { // bug 6893 + import std.array; enum E : ulong { A, B, C } auto stream = appender!(char[])(); formattedWrite(stream, "%s", E.C); assert(stream.data == "C"); } + +/***************************************************** + * Format arguments into a string. + * + * Params: fmt = Format string. For detailed specification, see $(XREF format,formattedWrite). + * args = Variadic list of arguments to format into returned string. + */ +string format(Char, Args...)(in Char[] fmt, Args args) +{ + import std.format : formattedWrite, FormatException; + import std.array : appender; + auto w = appender!string(); + auto n = formattedWrite(w, fmt, args); + version (all) + { + // In the future, this check will be removed to increase consistency + // with formattedWrite + import std.conv : text; + import std.exception : enforce; + enforce(n == args.length, new FormatException( + text("Orphan format arguments: args[", n, "..", args.length, "]"))); + } + return w.data; +} + +unittest +{ + import std.format; + import core.exception; + import std.exception; + assertCTFEable!( + { +// assert(format(null) == ""); + assert(format("foo") == "foo"); + assert(format("foo%%") == "foo%"); + assert(format("foo%s", 'C') == "fooC"); + assert(format("%s foo", "bar") == "bar foo"); + assert(format("%s foo %s", "bar", "abc") == "bar foo abc"); + assert(format("foo %d", -123) == "foo -123"); + assert(format("foo %d", 123) == "foo 123"); + + assertThrown!FormatException(format("foo %s")); + assertThrown!FormatException(format("foo %s", 123, 456)); + + assert(format("hel%slo%s%s%s", "world", -138, 'c', true) == + "helworldlo-138ctrue"); + }); +} + +/***************************************************** + * Format arguments into buffer buf which must be large + * enough to hold the result. Throws RangeError if it is not. + * Returns: The slice of $(D buf) containing the formatted string. + * + * $(RED sformat's current implementation has been replaced with $(LREF xsformat)'s + * implementation. in November 2012. + * This is seamless for most code, but it makes it so that the only + * argument that can be a format string is the first one, so any + * code which used multiple format strings has broken. Please change + * your calls to sformat accordingly. + * + * e.g.: + * ---- + * sformat(buf, "key = %s", key, ", value = %s", value) + * ---- + * needs to be rewritten as: + * ---- + * sformat(buf, "key = %s, value = %s", key, value) + * ---- + * ) + */ +char[] sformat(Char, Args...)(char[] buf, in Char[] fmt, Args args) +{ + import core.exception : onRangeError; + import std.utf : encode; + import std.format : formattedWrite, FormatException; + + size_t i; + + struct Sink + { + void put(dchar c) + { + char[4] enc; + auto n = encode(enc, c); + + if (buf.length < i + n) + onRangeError("std.string.sformat", 0); + + buf[i .. i + n] = enc[0 .. n]; + i += n; + } + void put(const(char)[] s) + { + if (buf.length < i + s.length) + onRangeError("std.string.sformat", 0); + + buf[i .. i + s.length] = s[]; + i += s.length; + } + void put(const(wchar)[] s) + { + for (; !s.empty; s.popFront()) + put(s.front); + } + void put(const(dchar)[] s) + { + for (; !s.empty; s.popFront()) + put(s.front); + } + } + auto n = formattedWrite(Sink(), fmt, args); + version (all) + { + // In the future, this check will be removed to increase consistency + // with formattedWrite + import std.conv : text; + import std.exception : enforce; + enforce(n == args.length, new FormatException( + text("Orphan format arguments: args[", n, "..", args.length, "]"))); + } + return buf[0 .. i]; +} + +unittest +{ + import core.exception; + import std.format; + + debug(string) trustedPrintf("std.string.sformat.unittest\n"); + + import std.exception; + assertCTFEable!( + { + char[10] buf; + + assert(sformat(buf[], "foo") == "foo"); + assert(sformat(buf[], "foo%%") == "foo%"); + assert(sformat(buf[], "foo%s", 'C') == "fooC"); + assert(sformat(buf[], "%s foo", "bar") == "bar foo"); + assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc")); + assert(sformat(buf[], "foo %d", -123) == "foo -123"); + assert(sformat(buf[], "foo %d", 123) == "foo 123"); + + assertThrown!FormatException(sformat(buf[], "foo %s")); + assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456)); + + assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); + }); +} diff --git a/std/functional.d b/std/functional.d index 93ed6df1523..753a4cd8845 100644 --- a/std/functional.d +++ b/std/functional.d @@ -22,39 +22,77 @@ module std.functional; import std.traits, std.typetuple; + +private template needOpCallAlias(alias fun) +{ + /* Determine whether or not unaryFun and binaryFun need to alias to fun or + * fun.opCall. Basically, fun is a function object if fun(...) compiles. We + * want is(unaryFun!fun) (resp., is(binaryFun!fun)) to be true if fun is + * any function object. There are 4 possible cases: + * + * 1) fun is the type of a function object with static opCall; + * 2) fun is an instance of a function object with static opCall; + * 3) fun is the type of a function object with non-static opCall; + * 4) fun is an instance of a function object with non-static opCall. + * + * In case (1), is(unaryFun!fun) should compile, but does not if unaryFun + * aliases itself to fun, because typeof(fun) is an error when fun itself + * is a type. So it must be aliased to fun.opCall instead. All other cases + * should be aliased to fun directly. + */ + static if (is(typeof(fun.opCall) == function)) + { + import std.traits : ParameterTypeTuple; + + enum needOpCallAlias = !is(typeof(fun)) && __traits(compiles, () { + return fun(ParameterTypeTuple!fun.init); + }); + } + else + enum needOpCallAlias = false; +} + /** Transforms a string representing an expression into a unary function. The string must either use symbol name $(D a) as the parameter or provide the symbol via the $(D parmName) argument. -If $(D fun) is not a string, $(D unaryFun) aliases itself away to -$(D fun). - -Example: - ----- -alias unaryFun!("(a & 1) == 0") isEven; -assert(isEven(2) && !isEven(1)); ----- +If $(D fun) is not a string, $(D unaryFun) aliases itself away to $(D fun). */ template unaryFun(alias fun, string parmName = "a") { static if (is(typeof(fun) : string)) { - import std.traits, std.typecons, std.typetuple; - import std.algorithm, std.conv, std.exception, std.math, std.range, std.string; + static if (!fun._ctfeMatchUnary(parmName)) + { + import std.traits, std.typecons, std.typetuple; + import std.algorithm, std.conv, std.exception, std.math, std.range, std.string; + } auto unaryFun(ElementType)(auto ref ElementType __a) { mixin("alias " ~ parmName ~ " = __a ;"); return mixin(fun); } } + else static if (needOpCallAlias!fun) + { + // Issue 9906 + alias unaryFun = fun.opCall; + } else { alias unaryFun = fun; } } +/// +unittest +{ + // Strings are compiled into functions: + alias isEven = unaryFun!("(a & 1) == 0"); + assert(isEven(2) && !isEven(1)); +} + /+ Undocumented, will be removed December 2014+/ deprecated("Parameter byRef is obsolete. Please call unaryFun!(fun, parmName) directly.") template unaryFun(alias fun, bool byRef, string parmName = "a") @@ -75,24 +113,41 @@ unittest int num = 41; assert(unaryFun!("a + 1", true)(num) == 42); + + // Issue 9906 + struct Seen + { + static bool opCall(int n) { return true; } + } + static assert(needOpCallAlias!Seen); + static assert(is(typeof(unaryFun!Seen(1)))); + assert(unaryFun!Seen(1)); + + Seen s; + static assert(!needOpCallAlias!s); + static assert(is(typeof(unaryFun!s(1)))); + assert(unaryFun!s(1)); + + struct FuncObj + { + bool opCall(int n) { return true; } + } + FuncObj fo; + static assert(!needOpCallAlias!fo); + static assert(is(typeof(unaryFun!fo))); + assert(unaryFun!fo(1)); + + // Function object with non-static opCall can only be called with an + // instance, not with merely the type. + static assert(!is(typeof(unaryFun!FuncObj))); } /** -Transforms a string representing an expression into a Boolean binary -predicate. The string must either use symbol names $(D a) and $(D b) -as the parameters or provide the symbols via the $(D parm1Name) and -$(D parm2Name) arguments. +Transforms a string representing an expression into a binary function. The +string must either use symbol names $(D a) and $(D b) as the parameters or +provide the symbols via the $(D parm1Name) and $(D parm2Name) arguments. If $(D fun) is not a string, $(D binaryFun) aliases itself away to $(D fun). - - Example: - ----- -alias less = binaryFun!("a < b"); -assert(less(1, 2) && !less(2, 1)); -alias greater = binaryFun!("a > b"); -assert(!greater("1", "2") && greater("2", "1")); ----- */ template binaryFun(alias fun, string parm1Name = "a", @@ -100,8 +155,11 @@ template binaryFun(alias fun, string parm1Name = "a", { static if (is(typeof(fun) : string)) { - import std.traits, std.typecons, std.typetuple; - import std.algorithm, std.conv, std.exception, std.math, std.range, std.string; + static if (!fun._ctfeMatchBinary(parm1Name, parm2Name)) + { + import std.traits, std.typecons, std.typetuple; + import std.algorithm, std.conv, std.exception, std.math, std.range, std.string; + } auto binaryFun(ElementType1, ElementType2) (auto ref ElementType1 __a, auto ref ElementType2 __b) { @@ -110,18 +168,28 @@ template binaryFun(alias fun, string parm1Name = "a", return mixin(fun); } } + else static if (needOpCallAlias!fun) + { + // Issue 9906 + alias binaryFun = fun.opCall; + } else { alias binaryFun = fun; } } +/// unittest { - alias less = binaryFun!(q{a < b}); + alias less = binaryFun!("a < b"); assert(less(1, 2) && !less(2, 1)); - assert(less("1", "2") && !less("2", "1")); + alias greater = binaryFun!("a > b"); + assert(!greater("1", "2") && greater("2", "1")); +} +unittest +{ static int f1(int a, string b) { return a + 1; } static assert(is(typeof(binaryFun!(f1)(1, "2")) == int)); assert(binaryFun!(f1)(41, "a") == 42); @@ -131,10 +199,193 @@ unittest assert(binaryFun!("a + b")(41, 1) == 42); //@@BUG //assert(binaryFun!("return a + b;")(41, 1) == 42); + + // Issue 9906 + struct Seen + { + static bool opCall(int x, int y) { return true; } + } + static assert(is(typeof(binaryFun!Seen))); + assert(binaryFun!Seen(1,1)); + + struct FuncObj + { + bool opCall(int x, int y) { return true; } + } + FuncObj fo; + static assert(!needOpCallAlias!fo); + static assert(is(typeof(binaryFun!fo))); + assert(unaryFun!fo(1,1)); + + // Function object with non-static opCall can only be called with an + // instance, not with merely the type. + static assert(!is(typeof(binaryFun!FuncObj))); +} + +// skip all ASCII chars except a..z, A..Z, 0..9, '_' and '.'. +private uint _ctfeSkipOp(ref string op) +{ + if (!__ctfe) assert(false); + import std.ascii : isASCII, isAlphaNum; + immutable oldLength = op.length; + while (op.length) + { + immutable front = op[0]; + if(front.isASCII() && !(front.isAlphaNum() || front == '_' || front == '.')) + op = op[1..$]; + else + break; + } + return oldLength != op.length; +} + +// skip all digits +private uint _ctfeSkipInteger(ref string op) +{ + if (!__ctfe) assert(false); + import std.ascii : isDigit; + immutable oldLength = op.length; + while (op.length) + { + immutable front = op[0]; + if(front.isDigit()) + op = op[1..$]; + else + break; + } + return oldLength != op.length; +} + +// skip name +private uint _ctfeSkipName(ref string op, string name) +{ + if (!__ctfe) assert(false); + if (op.length >= name.length && op[0..name.length] == name) + { + op = op[name.length..$]; + return 1; + } + return 0; +} + +// returns 1 if $(D fun) is trivial unary function +private uint _ctfeMatchUnary(string fun, string name) +{ + if (!__ctfe) assert(false); + import std.stdio; + fun._ctfeSkipOp(); + for (;;) + { + immutable h = fun._ctfeSkipName(name) + fun._ctfeSkipInteger(); + if (h == 0) + { + fun._ctfeSkipOp(); + break; + } + else if (h == 1) + { + if(!fun._ctfeSkipOp()) + break; + } + else + return 0; + } + return fun.length == 0; +} + +unittest +{ + static assert(!_ctfeMatchUnary("sqrt(ё)", "ё")); + static assert(!_ctfeMatchUnary("ё.sqrt", "ё")); + static assert(!_ctfeMatchUnary(".ё+ё", "ё")); + static assert(!_ctfeMatchUnary("_ё+ё", "ё")); + static assert(!_ctfeMatchUnary("ёё", "ё")); + static assert(_ctfeMatchUnary("a+a", "a")); + static assert(_ctfeMatchUnary("a + 10", "a")); + static assert(_ctfeMatchUnary("4 == a", "a")); + static assert(_ctfeMatchUnary("2==a", "a")); + static assert(_ctfeMatchUnary("1 != a", "a")); + static assert(_ctfeMatchUnary("a!=4", "a")); + static assert(_ctfeMatchUnary("a< 1", "a")); + static assert(_ctfeMatchUnary("434 < a", "a")); + static assert(_ctfeMatchUnary("132 > a", "a")); + static assert(_ctfeMatchUnary("123 >a", "a")); + static assert(_ctfeMatchUnary("a>82", "a")); + static assert(_ctfeMatchUnary("ё>82", "ё")); + static assert(_ctfeMatchUnary("ё[ё(ё)]", "ё")); + static assert(_ctfeMatchUnary("ё[21]", "ё")); +} + +// returns 1 if $(D fun) is trivial binary function +private uint _ctfeMatchBinary(string fun, string name1, string name2) +{ + if (!__ctfe) assert(false); + fun._ctfeSkipOp(); + for (;;) + { + immutable h = fun._ctfeSkipName(name1) + fun._ctfeSkipName(name2) + fun._ctfeSkipInteger(); + if (h == 0) + { + fun._ctfeSkipOp(); + break; + } + else if (h == 1) + { + if(!fun._ctfeSkipOp()) + break; + } + else + return 0; + } + return fun.length == 0; +} + +unittest { + + static assert(!_ctfeMatchBinary("sqrt(ё)", "ё", "b")); + static assert(!_ctfeMatchBinary("ё.sqrt", "ё", "b")); + static assert(!_ctfeMatchBinary(".ё+ё", "ё", "b")); + static assert(!_ctfeMatchBinary("_ё+ё", "ё", "b")); + static assert(!_ctfeMatchBinary("ёё", "ё", "b")); + static assert(_ctfeMatchBinary("a+a", "a", "b")); + static assert(_ctfeMatchBinary("a + 10", "a", "b")); + static assert(_ctfeMatchBinary("4 == a", "a", "b")); + static assert(_ctfeMatchBinary("2==a", "a", "b")); + static assert(_ctfeMatchBinary("1 != a", "a", "b")); + static assert(_ctfeMatchBinary("a!=4", "a", "b")); + static assert(_ctfeMatchBinary("a< 1", "a", "b")); + static assert(_ctfeMatchBinary("434 < a", "a", "b")); + static assert(_ctfeMatchBinary("132 > a", "a", "b")); + static assert(_ctfeMatchBinary("123 >a", "a", "b")); + static assert(_ctfeMatchBinary("a>82", "a", "b")); + static assert(_ctfeMatchBinary("ё>82", "ё", "q")); + static assert(_ctfeMatchBinary("ё[ё(10)]", "ё", "q")); + static assert(_ctfeMatchBinary("ё[21]", "ё", "q")); + + static assert(!_ctfeMatchBinary("sqrt(ё)+b", "b", "ё")); + static assert(!_ctfeMatchBinary("ё.sqrt-b", "b", "ё")); + static assert(!_ctfeMatchBinary(".ё+b", "b", "ё")); + static assert(!_ctfeMatchBinary("_b+ё", "b", "ё")); + static assert(!_ctfeMatchBinary("ba", "b", "a")); + static assert(_ctfeMatchBinary("a+b", "b", "a")); + static assert(_ctfeMatchBinary("a + b", "b", "a")); + static assert(_ctfeMatchBinary("b == a", "b", "a")); + static assert(_ctfeMatchBinary("b==a", "b", "a")); + static assert(_ctfeMatchBinary("b != a", "b", "a")); + static assert(_ctfeMatchBinary("a!=b", "b", "a")); + static assert(_ctfeMatchBinary("a< b", "b", "a")); + static assert(_ctfeMatchBinary("b < a", "b", "a")); + static assert(_ctfeMatchBinary("b > a", "b", "a")); + static assert(_ctfeMatchBinary("b >a", "b", "a")); + static assert(_ctfeMatchBinary("a>b", "b", "a")); + static assert(_ctfeMatchBinary("ё>b", "b", "ё")); + static assert(_ctfeMatchBinary("b[ё(-1)]", "b", "ё")); + static assert(_ctfeMatchBinary("ё[-21]", "b", "ё")); } -private template safeOp(string S) - if (is(typeof(mixin("0 "~S~" 0")) == bool)) +//undocumented +template safeOp(string S) + if (S=="<"||S==">"||S=="<="||S==">="||S=="=="||S=="!=") { private bool unsafeOp(ElementType1, ElementType2)(ElementType1 a, ElementType2 b) pure if (isIntegral!ElementType1 && isIntegral!ElementType2) @@ -143,7 +394,7 @@ private template safeOp(string S) return mixin("cast(T)a "~S~" cast(T)b"); } - private bool safeOp(T0, T1)(T0 a, T1 b) pure + bool safeOp(T0, T1)(auto ref T0 a, auto ref T1 b) { static if (isIntegral!T0 && isIntegral!T1 && (mostNegative!T0 < 0) != (mostNegative!T1 < 0)) @@ -174,16 +425,27 @@ private template safeOp(string S) } } +unittest //check user defined types +{ + import std.algorithm : equal; + struct Foo + { + int a; + auto opEquals(Foo foo) + { + return a == foo.a; + } + } + assert(safeOp!"!="(Foo(1), Foo(2))); +} + /** Predicate that returns $(D_PARAM a < b). Correctly compares signed and unsigned integers, ie. -1 < 2U. */ -bool lessThan(T0, T1)(T0 a, T1 b) -{ - return safeOp!"<"(a, b); -} +alias lessThan = safeOp!"<"; -unittest +pure @safe @nogc nothrow unittest { assert(lessThan(2, 3)); assert(lessThan(2U, 3U)); @@ -201,10 +463,7 @@ unittest Predicate that returns $(D_PARAM a > b). Correctly compares signed and unsigned integers, ie. 2U > -1. */ -bool greaterThan(T0, T1)(T0 a, T1 b) -{ - return safeOp!">"(a, b); -} +alias greaterThan = safeOp!">"; unittest { @@ -224,10 +483,7 @@ unittest Predicate that returns $(D_PARAM a == b). Correctly compares signed and unsigned integers, ie. !(-1 == ~0U). */ -bool equalTo(T0, T1)(T0 a, T1 b) -{ - return safeOp!"=="(a, b); -} +alias equalTo = safeOp!"=="; unittest { @@ -279,7 +535,7 @@ unittest template binaryReverseArgs(alias pred) { auto binaryReverseArgs(ElementType1, ElementType2) - (ElementType1 a, ElementType2 b) + (auto ref ElementType1 a, auto ref ElementType2 b) { return pred(b, a); } @@ -299,29 +555,45 @@ unittest /** Negates predicate $(D pred). - -Example: ----- -string a = " Hello, world!"; -assert(find!(not!isWhite)(a) == "Hello, world!"); ----- */ template not(alias pred) { - auto not(T...)(T args) - if (is(typeof(!unaryFun!pred(args))) || is(typeof(!binaryFun!pred(args)))) + auto not(T...)(auto ref T args) { - static if (T.length == 1) + static if (is(typeof(!pred(args)))) + return !pred(args); + else static if (T.length == 1) return !unaryFun!pred(args); else static if (T.length == 2) return !binaryFun!pred(args); else - static assert(false, "not implemented for multiple arguments"); + static assert(0); } } +/// +unittest +{ + import std.functional; + import std.algorithm : find; + import std.uni : isWhite; + string a = " Hello, world!"; + assert(find!(not!isWhite)(a) == "Hello, world!"); +} + +unittest +{ + assert(not!"a != 5"(5)); + assert(not!"a != b"(5, 5)); + + assert(not!(() => false)()); + assert(not!(a => a != 5)(5)); + assert(not!((a, b) => a != b)(5, 5)); + assert(not!((a, b, c) => a * b * c != 125 )(5, 5, 5)); +} + /** -$(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially +$(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially applies) $(D_PARAM fun) by tying its first argument to $(D_PARAM arg). Example: @@ -499,7 +771,7 @@ template adjoin(F...) if (F.length > 1) } else { - import std.string : format; + import std.format : format; import std.range : iota; return mixin (q{tuple(%(F[%s](a)%|, %))}.format(iota(0, F.length))); } @@ -702,76 +974,131 @@ Technically the memoized function should be pure because $(D memoize) assumes it always return the same result for a given tuple of arguments. However, $(D memoize) does not enforce that because sometimes it is useful to memoize an impure function, too. +*/ +template memoize(alias fun) +{ + // alias Args = ParameterTypeTuple!fun; // Bugzilla 13580 -To _memoize a recursive function, simply insert the memoized call in lieu of the plain recursive call. -For example, to transform the exponential-time Fibonacci implementation into a linear-time computation: + ReturnType!fun memoize(ParameterTypeTuple!fun args) + { + alias Args = ParameterTypeTuple!fun; + import std.typecons : Tuple; -Example: ----- -ulong fib(ulong n) -{ - alias mfib = memoize!fib; - return n < 2 ? 1 : mfib(n - 2) + mfib(n - 1); + static ReturnType!fun[Tuple!Args] memo; + auto t = Tuple!Args(args); + if (auto p = t in memo) + return *p; + return memo[t] = fun(args); + } } -... -assert(fib(10) == 89); ----- - -To improve the speed of the factorial function, -Example: ----- -ulong fact(ulong n) +/// ditto +template memoize(alias fun, uint maxSize) { - alias mfact = memoize!fact; - return n < 2 ? 1 : n * mfact(n - 1); + // alias Args = ParameterTypeTuple!fun; // Bugzilla 13580 + ReturnType!fun memoize(ParameterTypeTuple!fun args) + { + import std.typecons : tuple; + static struct Value { ParameterTypeTuple!fun args; ReturnType!fun res; } + static Value[] memo; + static size_t[] initialized; + + if (!memo.length) + { + import core.memory; + + enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); + memo = (cast(Value*)GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; + enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); + initialized = (cast(size_t*)GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; + } + + import core.bitop : bt, bts; + import std.conv : emplace; + + size_t hash; + foreach (ref arg; args) + hash = hashOf(arg, hash); + // cuckoo hashing + immutable idx1 = hash % maxSize; + if (!bt(initialized.ptr, idx1)) + { + emplace(&memo[idx1], args, fun(args)); + bts(initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025) + return memo[idx1].res; + } + else if (memo[idx1].args == args) + return memo[idx1].res; + // FNV prime + immutable idx2 = (hash * 16777619) % maxSize; + if (!bt(initialized.ptr, idx2)) + { + emplace(&memo[idx2], memo[idx1]); + bts(initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025) + } + else if (memo[idx2].args == args) + return memo[idx2].res; + else if (idx1 != idx2) + memo[idx2] = memo[idx1]; + + memo[idx1] = Value(args, fun(args)); + return memo[idx1].res; + } } -... -assert(fact(10) == 3628800); ----- -This memoizes all values of $(D fact) up to the largest argument. To only cache the final -result, move $(D memoize) outside the function as shown below. +/** + * To _memoize a recursive function, simply insert the memoized call in lieu of the plain recursive call. + * For example, to transform the exponential-time Fibonacci implementation into a linear-time computation: + */ +unittest +{ + ulong fib(ulong n) + { + return n < 2 ? 1 : memoize!fib(n - 2) + memoize!fib(n - 1); + } + assert(fib(10) == 89); +} -Example: ----- -ulong factImpl(ulong n) +/** + * To improve the speed of the factorial function, + */ +unittest { - return n < 2 ? 1 : n * factImpl(n - 1); + ulong fact(ulong n) + { + return n < 2 ? 1 : n * memoize!fact(n - 1); + } + assert(fact(10) == 3628800); } -alias fact = memoize!factImpl; -... -assert(fact(10) == 3628800); ----- -The $(D maxSize) parameter is a cutoff for the cache size. If upon a miss the length of the hash -table is found to be $(D maxSize), the table is simply cleared. +/** + * This memoizes all values of $(D fact) up to the largest argument. To only cache the final + * result, move $(D memoize) outside the function as shown below. + */ +unittest +{ + ulong factImpl(ulong n) + { + return n < 2 ? 1 : n * factImpl(n - 1); + } + alias fact = memoize!factImpl; + assert(fact(10) == 3628800); +} -Example: ----- -// Memoize no more than 128 values of transmogrify -alias fastTransmogrify = memoize!(transmogrify, 128); ----- -*/ -template memoize(alias fun, uint maxSize = uint.max) +/** + * When the $(D maxSize) parameter is specified, memoize will used + * a fixed size hash table to limit the number of cached entries. + */ +unittest { - private alias Args = ParameterTypeTuple!fun; - ReturnType!fun memoize(Args args) + ulong fact(ulong n) { - import std.typecons : Tuple, tuple; - static ReturnType!fun[Tuple!Args] memo; - auto t = Tuple!Args(args); - auto p = t in memo; - if (p) return *p; - static if (maxSize != uint.max) - { - if (memo.length >= maxSize) memo = null; - } - auto r = fun(args); - //writeln("Inserting result ", typeof(r).stringof, "(", r, ") for parameters ", t); - memo[t] = r; - return r; + // Memoize no more than 8 values + return n < 2 ? 1 : n * memoize!(fact, 8)(n - 1); } + assert(fact(8) == 40320); + // using more entries than maxSize will overwrite existing entries + assert(fact(10) == 3628800); } unittest @@ -813,6 +1140,11 @@ unittest else return 1 + mLen2(s[1 .. $]); } + + int _func(int x) { return 1; } + alias func = memoize!(_func, 10); + assert(func(int.init) == 1); + assert(func(int.init) == 1); } private struct DelegateFaker(F) @@ -1027,4 +1359,3 @@ unittest { static assert(! is(typeof(dg_xtrnC) == typeof(dg_xtrnD))); } } - diff --git a/std/getopt.d b/std/getopt.d index da4547f695e..913ef7f1fbf 100644 --- a/std/getopt.d +++ b/std/getopt.d @@ -15,7 +15,7 @@ Macros: WIKI = Phobos/StdGetopt Copyright: Copyright Andrei Alexandrescu 2008 - 2009. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(WEB erdani.org, Andrei Alexandrescu) Credits: This module and its documentation are inspired by Perl's $(WEB perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of @@ -32,13 +32,7 @@ Distributed under the Boost Software License, Version 1.0. */ module std.getopt; -private import std.array, std.string, std.conv, std.traits, std.bitmanip, - std.algorithm, std.ascii, std.exception, std.typetuple, std.typecons; - -version (unittest) -{ - import std.stdio; // for testing only -} +import std.traits; /** * Thrown on one of the following conditions: @@ -125,7 +119,7 @@ void main(string[] args) To set $(D debugging) to $(D false), invoke the program with $(D --debugging=false). ) - + $(LI $(I Numeric options.) If an option is bound to a numeric type, a number is expected as the next option, or right within the option separated with an "=" sign: @@ -242,7 +236,7 @@ getopt(args, "tune", &tuningParms); $(LI If the callback doesn't take any arguments, the callback is invoked whenever the option is seen. ) - + $(LI If the callback takes one string argument, the option string (without the leading dash(es)) is passed to the callback. After that, the option string is considered handled and removed from the options @@ -269,7 +263,7 @@ void main(string[] args) --------- ) - + $(LI If the callback takes two string arguments, the option string is handled as an option with one argument, and parsed accordingly. The option and its value are passed to the callback. After that, whatever @@ -418,7 +412,7 @@ $(D args) after $(D getopt) returns. $(D Help Information Generation) If an option string is followed by another string, this string serves as an -description for this option. The function $(D getopt) returns a struct of type +description for this option. The function $(D getopt) returns a struct of type $(D GetoptResult). This return value contains information about all passed options as well a bool indicating if information about these options where required by the passed arguments. @@ -431,7 +425,9 @@ to another program). Invoking the example above with $(D "--foo -- --bar") parses foo but leaves "--bar" in $(D args). The double-dash itself is removed from the argument array. */ -GetoptResult getopt(T...)(ref string[] args, T opts) { +GetoptResult getopt(T...)(ref string[] args, T opts) +{ + import std.exception : enforce; enforce(args.length, "Invalid arguments string passed: program name missing"); configuration cfg; @@ -449,10 +445,10 @@ unittest bool foo; bool bar; - auto rslt = getopt(args, "foo|f" "Some information about foo.", &foo, "bar|b", + auto rslt = getopt(args, "foo|f" "Some information about foo.", &foo, "bar|b", "Some help message about bar.", &bar); - if (rslt.helpWanted) + if (rslt.helpWanted) { defaultGetoptPrinter("Some information about the program.", rslt.options); @@ -484,10 +480,10 @@ enum config { required } -/** The result of the $(D getoptX) function. +/** The result of the $(D getopt) function. -The $(D GetOptDRslt) contains two members. The first member is a boolean with -the name $(D helpWanted). The second member is an array of $(D Option). The +The $(D GetoptResult) contains two members. The first member is a boolean with +the name $(D helpWanted). The second member is an array of $(D Option). The array is accessable by the name $(D options). */ struct GetoptResult { @@ -505,18 +501,19 @@ struct Option { /// an error. } -pure Option splitAndGet(string opt) @trusted nothrow +private pure Option splitAndGet(string opt) @trusted nothrow { + import std.array : split; auto sp = split(opt, "|"); Option ret; - if (sp.length > 1) + if (sp.length > 1) { - ret.optShort = "-" ~ (sp[0].length < sp[1].length ? + ret.optShort = "-" ~ (sp[0].length < sp[1].length ? sp[0] : sp[1]); - ret.optLong = "--" ~ (sp[0].length > sp[1].length ? + ret.optLong = "--" ~ (sp[0].length > sp[1].length ? sp[0] : sp[1]); - } - else + } + else { ret.optLong = "--" ~ sp[0]; } @@ -524,9 +521,11 @@ pure Option splitAndGet(string opt) @trusted nothrow return ret; } -private void getoptImpl(T...)(ref string[] args, ref configuration cfg, +private void getoptImpl(T...)(ref string[] args, ref configuration cfg, ref GetoptResult rslt, T opts) { + import std.algorithm : remove; + import std.conv : to; static if (opts.length) { static if (is(typeof(opts[0]) : config)) @@ -568,7 +567,7 @@ private void getoptImpl(T...)(ref string[] args, ref configuration cfg, if (cfg.required && !optWasHandled) { - throw new GetOptException("Required option " ~ option ~ + throw new GetOptException("Required option " ~ option ~ "was not supplied"); } cfg.required = false; @@ -616,12 +615,16 @@ private void getoptImpl(T...)(ref string[] args, ref configuration cfg, } } -bool handleOption(R)(string option, R receiver, ref string[] args, +private bool handleOption(R)(string option, R receiver, ref string[] args, ref configuration cfg, bool incremental) { + import std.algorithm : map, splitter; + import std.ascii : isAlpha; + import std.conv : text, to; // Scan arguments looking for a match for this option bool ret = false; - for (size_t i = 1; i < args.length; ) { + for (size_t i = 1; i < args.length; ) + { auto a = args[i]; if (endOfOptions.length && a == endOfOptions) break; if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar)) @@ -678,12 +681,14 @@ bool handleOption(R)(string option, R receiver, ref string[] args, } else { + import std.exception : enforce; // non-boolean option, which might include an argument //enum isCallbackWithOneParameter = is(typeof(receiver("")) : void); enum isCallbackWithLessThanTwoParameters = (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) && !is(typeof(receiver("", ""))); - if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental) { + if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental) + { // Eat the next argument too. Check to make sure there's one // to be eaten first, though. enforce(i < args.length, @@ -751,10 +756,11 @@ bool handleOption(R)(string option, R receiver, ref string[] args, import std.range : only; import std.typecons : Tuple, tuple; + import std.string : indexOf; static Tuple!(K, V) getter(string input) { - auto j = std.string.indexOf(input, assignChar); + auto j = indexOf(input, assignChar); auto key = input[0 .. j]; auto value = input[j + 1 .. $]; return tuple(to!K(key), to!V(value)); @@ -785,6 +791,8 @@ bool handleOption(R)(string option, R receiver, ref string[] args, // 5316 - arrays with arraySep unittest { + import std.conv; + arraySep = ","; scope (exit) arraySep = ""; @@ -812,6 +820,8 @@ unittest // 5316 - associative arrays with arraySep unittest { + import std.conv; + arraySep = ","; scope (exit) arraySep = ""; @@ -871,6 +881,7 @@ enum autoIncrementChar = '+'; private struct configuration { + import std.bitmanip : bitfields; mixin(bitfields!( bool, "caseSensitive", 1, bool, "bundling", 1, @@ -883,6 +894,9 @@ private struct configuration private bool optMatch(string arg, string optPattern, ref string value, configuration cfg) { + import std.uni : toUpper; + import std.string : indexOf; + import std.array : split; //writeln("optMatch:\n ", arg, "\n ", optPattern, "\n ", value); //scope(success) writeln("optMatch result: ", value); if (!arg.length || arg[0] != optionChar) return false; @@ -892,7 +906,7 @@ private bool optMatch(string arg, string optPattern, ref string value, //writeln("isLong: ", isLong); // yank the second '-' if present if (isLong) arg = arg[1 .. $]; - immutable eqPos = std.string.indexOf(arg, assignChar); + immutable eqPos = indexOf(arg, assignChar); if (isLong && eqPos >= 0) { // argument looks like --opt=value @@ -922,7 +936,7 @@ private bool optMatch(string arg, string optPattern, ref string value, if (arg == v || !cfg.caseSensitive && toUpper(arg) == toUpper(v)) return true; if (cfg.bundling && !isLong && v.length == 1 - && std.string.indexOf(arg, v) >= 0) + && indexOf(arg, v) >= 0) { //writeln("success"); return true; @@ -950,7 +964,9 @@ private void setConfig(ref configuration cfg, config option) unittest { + import std.conv; import std.math; + uint paranoid = 2; string[] args = (["program.name", "--paranoid", "--paranoid", "--paranoid"]).dup; @@ -1183,6 +1199,9 @@ unittest unittest // 5228 { + import std.exception; + import std.conv; + auto args = ["prog", "--foo=bar"]; int abc; assertThrown!GetOptException(getopt(args, "abc", &abc)); @@ -1193,6 +1212,8 @@ unittest // 5228 unittest // From bugzilla 7693 { + import std.exception; + enum Foo { bar, baz @@ -1211,6 +1232,8 @@ unittest // From bugzilla 7693 unittest // same bug as 7693 only for bool { + import std.exception; + auto args = ["prog", "--foo=truefoobar"]; bool foo; assertThrown(getopt(args, "foo", &foo)); @@ -1219,7 +1242,7 @@ unittest // same bug as 7693 only for bool assert(foo); } -unittest +unittest { bool foo; auto args = ["prog", "--foo"]; @@ -1243,8 +1266,8 @@ unittest bool foo; bool bar; auto args = ["prog", "-b", "--foo", "-z"]; - getopt(args, config.caseInsensitive, config.required, "foo|f" "Some foo", - &foo, config.caseSensitive, "bar|b", "Some bar", &bar, + getopt(args, config.caseInsensitive, config.required, "foo|f" "Some foo", + &foo, config.caseSensitive, "bar|b", "Some bar", &bar, config.passThrough); assert(foo); assert(bar); @@ -1252,21 +1275,25 @@ unittest unittest { + import std.exception; + bool foo; bool bar; auto args = ["prog", "-b", "-z"]; assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f", - "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar, + "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar, config.passThrough)); } unittest { + import std.exception; + bool foo; bool bar; auto args = ["prog", "--foo", "-z"]; - assertNotThrown(getopt(args, config.caseInsensitive, config.required, - "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", + assertNotThrown(getopt(args, config.caseInsensitive, config.required, + "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar, config.passThrough)); assert(foo); assert(!bar); @@ -1318,16 +1345,17 @@ unittest assert(args == ["program", "--option"]); } -/** This function prints the passed $(D Option) and text in an aligned manner. +/** This function prints the passed $(D Option) and text in an aligned manner. The passed text will be printed first, followed by a newline. Than the short and long version of every option will be printed. The short and long version will be aligned to the longest option of every $(D Option) passed. If a help -message is present it will be printed after the long version of the +message is present it will be printed after the long version of the $(D Option). ------------ -foreach(it; opt) { +foreach(it; opt) +{ writefln("%*s %*s %s", lengthOfLongestShortOption, it.optShort, lengthOfLongestLongOption, it.optLong, it.help); } @@ -1335,9 +1363,9 @@ foreach(it; opt) { Params: text = The text to printed at the beginning of the help output. - opt = The $(D Option) extracted from the $(D getoptX) parameter. + opt = The $(D Option) extracted from the $(D getopt) parameter. */ -void defaultGetoptPrinter(string text, Option[] opt) +void defaultGetoptPrinter(string text, Option[] opt) { import std.stdio : stdout; @@ -1345,25 +1373,27 @@ void defaultGetoptPrinter(string text, Option[] opt) } /** This function writes the passed text and $(D Option) into an output range -in the manner, described in the documentation of function -$(D defaultGetoptXPrinter). +in the manner, described in the documentation of function +$(D defaultGetoptPrinter). Params: output = The output range used to write the help information. text = The text to printed at the beginning of the help output. - opt = The $(D Option) extracted from the $(D getoptX) parameter. + opt = The $(D Option) extracted from the $(D getopt) parameter. */ -void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) { +void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) +{ import std.format : formattedWrite; + import std.algorithm : min, max; output.formattedWrite("%s\n", text); size_t ls, ll; bool hasRequired = false; - foreach (it; opt) + foreach (it; opt) { - ls = max(ls, it.optShort.length); - ll = max(ll, it.optLong.length); + ls = max(ls, it.optShort.length); + ll = max(ll, it.optLong.length); hasRequired = hasRequired || it.required; } @@ -1372,7 +1402,7 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) { string re = " Required: "; - foreach (it; opt) + foreach (it; opt) { output.formattedWrite("%*s %*s%*s%s\n", ls, it.optShort, ll, it.optLong, hasRequired ? re.length : 1, it.required ? re : " ", it.help); @@ -1381,6 +1411,10 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) { unittest { + import std.conv; + + import std.array; + import std.string; bool a; auto args = ["prog", "--foo"]; auto t = getopt(args, "foo|f", "Help", &a); @@ -1398,7 +1432,7 @@ unittest assert(helpMsg.indexOf("-h") != -1); assert(helpMsg.indexOf("--help") != -1); assert(helpMsg.indexOf("Help") != -1); - + string wanted = "Some Text\n-f --foo Help\n-h --help This help " ~ "information.\n"; assert(wanted == helpMsg); @@ -1406,6 +1440,9 @@ unittest unittest { + import std.conv; + import std.string; + import std.array ; bool a; auto args = ["prog", "--foo"]; auto t = getopt(args, config.required, "foo|f", "Help", &a); @@ -1424,8 +1461,8 @@ unittest assert(helpMsg.indexOf("-h") != -1); assert(helpMsg.indexOf("--help") != -1); assert(helpMsg.indexOf("Help") != -1); - - string wanted = "Some Text\n-f --foo Required: Help\n-h --help " + + string wanted = "Some Text\n-f --foo Required: Help\n-h --help " " This help information.\n"; assert(wanted == helpMsg, helpMsg ~ wanted); } diff --git a/std/internal/cstring.d b/std/internal/cstring.d index 3c5620022dc..344210a402c 100644 --- a/std/internal/cstring.d +++ b/std/internal/cstring.d @@ -37,13 +37,7 @@ COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, $(D core.$1.$2)) module std.internal.cstring; -static import core.stdc.stdlib; -static import core.checkedint; -import core.exception; - import std.traits; -import std.utf; - version(unittest) @property inout(C)[] asArray(C)(inout C* cstr) pure nothrow @nogc @@ -84,6 +78,9 @@ See $(RED WARNING) in $(B Examples) section. auto tempCString(To = char, From)(in From[] str) nothrow @nogc if(isSomeChar!To && isSomeChar!From) { + import core.checkedint : addu; + import core.exception : onOutOfMemoryError; + enum useStack = cast(To*) -1; static struct Res @@ -121,7 +118,7 @@ if(isSomeChar!To && isSomeChar!From) // Note: res._ptr can't point to res._buff as structs are movable. bool overflow = false; - const totalCount = core.checkedint.addu(maxLength!To(str), 1, overflow); + const totalCount = addu(maxLength!To(str), 1, overflow); if(overflow) onOutOfMemoryError(); const needAllocate = totalCount > res._buff.length; @@ -231,6 +228,7 @@ if(isSomeChar!To && isSomeChar!From) } else { + import std.utf : byChar, byWchar, byDchar; alias GenericTuple(Args...) = Args; alias byFunc = GenericTuple!(byChar, byWchar, null, byDchar)[To.sizeof - 1]; @@ -262,6 +260,7 @@ pure nothrow @nogc unittest pure unittest { import std.range; + import std.utf : toUTF16, toUTF32; const str = "abc-ЭЮЯ"; char[100] sbuff; @@ -297,8 +296,11 @@ if(T.alignof <= mallocAlignment) in { assert(count); } body { + import core.exception : onOutOfMemoryError; + import core.checkedint: mulu; + bool overflow = false; - const buffBytes = core.checkedint.mulu(T.sizeof, count, overflow); + const buffBytes = mulu(T.sizeof, count, overflow); if(overflow) onOutOfMemoryError(); @@ -313,6 +315,7 @@ void* tryRawAllocate(in size_t count) nothrow @nogc in { assert(count); } body { + import core.stdc.stdlib: malloc; // Workaround snn @@@BUG11646@@@ version(DigitalMars) version(Win32) if(count > 0xD5550000) return null; @@ -320,10 +323,11 @@ body // FIXME: `malloc` must be checked on every C runtime for // possible bugs and workarounded if necessary. - return core.stdc.stdlib.malloc(count); + return malloc(count); } void rawFree(void* ptr) nothrow @nogc { - core.stdc.stdlib.free(ptr); + import core.stdc.stdlib: free; + free(ptr); } diff --git a/std/internal/digest/sha_SSSE3.d b/std/internal/digest/sha_SSSE3.d index c1f8a3d0536..36b2414fc73 100644 --- a/std/internal/digest/sha_SSSE3.d +++ b/std/internal/digest/sha_SSSE3.d @@ -199,12 +199,12 @@ version(USE_SSSE3) */ private nothrow pure string wrap(string[] insn) { - string s = "asm {"; + string s = "asm pure nothrow @nogc {"; foreach (t; insn) s ~= (t ~ "; \n"); s ~= "}"; return s; // Is not CTFE: - // return "asm { " ~ join(insn, "; \n") ~ "}"; + // return "asm pure nothrow @nogc { " ~ join(insn, "; \n") ~ "}"; } /** diff --git a/std/internal/math/biguintcore.d b/std/internal/math/biguintcore.d index cbe234193b2..b73ea2b8e27 100644 --- a/std/internal/math/biguintcore.d +++ b/std/internal/math/biguintcore.d @@ -82,10 +82,10 @@ template maxBigDigits(T) if (isIntegral!T) enum maxBigDigits = (T.sizeof+BigDigit.sizeof-1)/BigDigit.sizeof; } -enum immutable(BigDigit) [] ZERO = [0]; -enum immutable(BigDigit) [] ONE = [1]; -enum immutable(BigDigit) [] TWO = [2]; -enum immutable(BigDigit) [] TEN = [10]; +static immutable BigDigit[] ZERO = [0]; +static immutable BigDigit[] ONE = [1]; +static immutable BigDigit[] TWO = [2]; +static immutable BigDigit[] TEN = [10]; public: @@ -99,17 +99,21 @@ private: assert( data.length >= 1 && (data.length == 1 || data[$-1] != 0 )); } immutable(BigDigit) [] data = ZERO; - this(immutable(BigDigit) [] x) pure nothrow + this(immutable(BigDigit) [] x) pure nothrow @safe { data = x; } - this(T)(T x) pure nothrow if (isIntegral!T) + this(T)(T x) pure nothrow @safe if (isIntegral!T) { opAssign(x); } + + enum trustedAssumeUnique = function(BigDigit[] input) pure @trusted @nogc { + return assumeUnique(input); + }; public: // Length in uints - @property size_t uintLength() pure nothrow const + @property size_t uintLength() pure nothrow const @safe @nogc { static if (BigDigit.sizeof == uint.sizeof) { @@ -121,7 +125,7 @@ public: ((data[$-1] & 0xFFFF_FFFF_0000_0000L) ? 1 : 0); } } - @property size_t ulongLength() pure nothrow const + @property size_t ulongLength() pure nothrow const @safe @nogc { static if (BigDigit.sizeof == uint.sizeof) { @@ -134,7 +138,7 @@ public: } // The value at (cast(ulong[])data)[n] - ulong peekUlong(int n) pure nothrow const + ulong peekUlong(int n) pure nothrow const @safe @nogc { static if (BigDigit.sizeof == int.sizeof) { @@ -146,7 +150,7 @@ public: return data[n]; } } - uint peekUint(int n) pure nothrow const + uint peekUint(int n) pure nothrow const @safe @nogc { static if (BigDigit.sizeof == int.sizeof) { @@ -160,7 +164,7 @@ public: } public: /// - void opAssign(Tulong)(Tulong u) pure nothrow if (is (Tulong == ulong)) + void opAssign(Tulong)(Tulong u) pure nothrow @safe if (is (Tulong == ulong)) { if (u == 0) data = ZERO; else if (u == 1) data = ONE; @@ -187,13 +191,13 @@ public: } } } - void opAssign(Tdummy = void)(BigUint y) pure nothrow + void opAssign(Tdummy = void)(BigUint y) pure nothrow @safe { this.data = y.data; } /// - int opCmp(Tdummy = void)(const BigUint y) pure const + int opCmp(Tdummy = void)(const BigUint y) pure const @safe { if (data.length != y.data.length) return (data.length > y.data.length) ? 1 : -1; @@ -204,7 +208,7 @@ public: } /// - int opCmp(Tulong)(Tulong y) pure const if(is (Tulong == ulong)) + int opCmp(Tulong)(Tulong y) pure const @safe if(is (Tulong == ulong)) { if (data.length > maxBigDigits!Tulong) return 1; @@ -230,12 +234,12 @@ public: return 0; } - bool opEquals(Tdummy = void)(ref const BigUint y) pure const + bool opEquals(Tdummy = void)(ref const BigUint y) pure const @safe { return y.data[] == data[]; } - bool opEquals(Tdummy = void)(ulong y) pure const + bool opEquals(Tdummy = void)(ulong y) pure const @safe { if (data.length > 2) return false; @@ -248,12 +252,12 @@ public: return (data[0] == ylo); } - bool isZero() pure const nothrow @safe + bool isZero() pure const nothrow @safe @nogc { return data.length == 1 && data[0] == 0; } - size_t numBytes() pure const + size_t numBytes() pure const @safe @nogc { return data.length * BigDigit.sizeof; } @@ -275,7 +279,7 @@ public: * Separator characters do not contribute to the minPadding. */ char [] toHexString(int frontExtraBytes, char separator = 0, - int minPadding=0, char padChar = '0') const pure + int minPadding=0, char padChar = '0') const pure @safe { // Calculate number of extra padding bytes size_t extraPad = (minPadding > data.length * 2 * BigDigit.sizeof) @@ -337,7 +341,7 @@ public: } // return false if invalid character found - bool fromHexString(const(char)[] s) pure nothrow + bool fromHexString(const(char)[] s) pure nothrow @safe { //Strip leading zeros int firstNonZero = 0; @@ -379,13 +383,16 @@ public: tmp[sofar] = part; ++sofar; } - if (sofar == 0) data = ZERO; - else data = assumeUnique(tmp[0..sofar]); + if (sofar == 0) + data = ZERO; + else + data = trustedAssumeUnique(tmp[0 .. sofar]); + return true; } // return true if OK; false if erroneous characters found - bool fromDecimalString(const(char)[] s) pure + bool fromDecimalString(const(char)[] s) pure @trusted { //Strip leading zeros int firstNonZero = 0; @@ -405,7 +412,7 @@ public: uint hi = biguintFromDecimal(tmp, s[firstNonZero..$]); tmp.length = hi; - data = assumeUnique(tmp); + data = trustedAssumeUnique(tmp); return true; } @@ -428,8 +435,11 @@ public: { uint [] result = new BigDigit[data.length - words]; multibyteShr(result, data[words..$], bits); - if (result.length>1 && result[$-1]==0) return BigUint(assumeUnique(result[0..$-1])); - else return BigUint(assumeUnique(result)); + + if (result.length > 1 && result[$-1] == 0) + return BigUint(trustedAssumeUnique(result[0 .. $-1])); + else + return BigUint(trustedAssumeUnique(result)); } } @@ -446,14 +456,14 @@ public: if (bits==0) { result[words..words+data.length] = data[]; - return BigUint(assumeUnique(result[0..words+data.length])); + return BigUint(trustedAssumeUnique(result[0..words+data.length])); } else { uint c = multibyteShl(result[words..words+data.length], data, bits); - if (c==0) return BigUint(assumeUnique(result[0..words+data.length])); + if (c==0) return BigUint(trustedAssumeUnique(result[0..words+data.length])); result[$-1] = c; - return BigUint(assumeUnique(result)); + return BigUint(trustedAssumeUnique(result)); } } @@ -545,7 +555,7 @@ public: result[x.data.length+1] = multibyteMulAdd!('+')(result[1..x.data.length+1], x.data, hi, 0); } - return BigUint(removeLeadingZeros(assumeUnique(result))); + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); } /* return x * y. @@ -567,7 +577,7 @@ public: } // the highest element could be zero, // in which case we need to reduce the length - return BigUint(removeLeadingZeros(assumeUnique(result))); + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); } // return x / y @@ -594,7 +604,7 @@ public: result[] = x.data[]; uint rem = multibyteDivAssign(result, y, 0); } - return BigUint(removeLeadingZeros(assumeUnique(result))); + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); } static BigUint divInt(T)(BigUint x, T y) pure nothrow @@ -609,7 +619,7 @@ public: immutable uint[2] z = [lo, hi]; BigDigit[] result = new BigDigit[x.data.length - z.length + 1]; divModInternal(result, null, x.data, z[]); - return BigUint(removeLeadingZeros(assumeUnique(result))); + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); } // return x % y @@ -640,8 +650,8 @@ public: if (y.data.length == 1) return divInt(x, y.data[0]); BigDigit [] result = new BigDigit[x.data.length - y.data.length + 1]; - divModInternal(result, null, x.data, y.data); - return BigUint(removeLeadingZeros(assumeUnique(result))); + divModInternal(result, null, x.data, y.data); + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); } // return x % y @@ -655,12 +665,12 @@ public: BigDigit [] result = new BigDigit[x.data.length - y.data.length + 1]; BigDigit [] rem = new BigDigit[y.data.length]; divModInternal(result, rem, x.data, y.data); - return BigUint(removeLeadingZeros(assumeUnique(rem))); + return BigUint(removeLeadingZeros(trustedAssumeUnique(rem))); } // return x op y static BigUint bitwiseOp(string op)(BigUint x, BigUint y, bool xSign, bool ySign, ref bool resultSign) - pure nothrow if (op == "|" || op == "^" || op == "&") + pure nothrow @safe if (op == "|" || op == "^" || op == "&") { auto d1 = includeSign(x.data, y.uintLength, xSign); auto d2 = includeSign(y.data, x.uintLength, ySign); @@ -672,11 +682,12 @@ public: mixin("resultSign = xSign " ~ op ~ " ySign;"); - if (resultSign) { + if (resultSign) + { twosComplement(d1, d1); } - return BigUint(removeLeadingZeros(assumeUnique(d1))); + return BigUint(removeLeadingZeros(trustedAssumeUnique(d1))); } /** @@ -890,7 +901,7 @@ public: { r1=r1[0 .. $ - 1]; } - return BigUint(assumeUnique(resultBuffer[0 .. result_start + r1.length])); + return BigUint(trustedAssumeUnique(resultBuffer[0 .. result_start + r1.length])); } // Implement toHash so that BigUint works properly as an AA key. @@ -901,7 +912,7 @@ public: } // end BigUint -unittest +@safe pure unittest { // ulong comparison test BigUint a = [1]; @@ -916,14 +927,14 @@ unittest } // Remove leading zeros from x, to restore the BigUint invariant -inout(BigDigit) [] removeLeadingZeros(inout(BigDigit) [] x) pure nothrow +inout(BigDigit) [] removeLeadingZeros(inout(BigDigit) [] x) pure nothrow @safe { size_t k = x.length; while(k>1 && x[k - 1]==0) --k; return x[0 .. k]; } -unittest +pure unittest { BigUint r = BigUint([5]); BigUint t = BigUint([7]); @@ -932,7 +943,7 @@ unittest } -unittest +@safe pure unittest { BigUint r; r = 5UL; @@ -945,7 +956,7 @@ unittest // Pow tests -unittest +pure unittest { BigUint r, s; r.fromHexString("80000000_00000001"); @@ -969,7 +980,7 @@ unittest } // Radix conversion tests -unittest +@safe pure unittest { BigUint r; r.fromHexString("1_E1178E81_00000000"); @@ -992,7 +1003,8 @@ unittest private: -void twosComplement(const(BigDigit) [] x, BigDigit[] result) pure nothrow +void twosComplement(const(BigDigit) [] x, BigDigit[] result) +pure nothrow @safe { foreach (i; 0..x.length) { @@ -1002,10 +1014,14 @@ void twosComplement(const(BigDigit) [] x, BigDigit[] result) pure nothrow bool sgn = false; - foreach (i; 0..result.length) { - if (result[i] == BigDigit.max) { + foreach (i; 0..result.length) + { + if (result[i] == BigDigit.max) + { result[i] = 0; - } else { + } + else + { result[i] += 1; break; } @@ -1014,7 +1030,7 @@ void twosComplement(const(BigDigit) [] x, BigDigit[] result) pure nothrow // Encode BigInt as BigDigit array (sign and 2's complement) BigDigit[] includeSign(const(BigDigit) [] x, size_t minSize, bool sign) -pure nothrow +pure nothrow @safe { size_t length = (x.length > minSize) ? x.length : minSize; BigDigit [] result = new BigDigit[length]; @@ -1030,7 +1046,7 @@ pure nothrow } // works for any type -T intpow(T)(T x, ulong n) pure nothrow +T intpow(T)(T x, ulong n) pure nothrow @safe { T p; @@ -1065,7 +1081,7 @@ T intpow(T)(T x, ulong n) pure nothrow // returns the maximum power of x that will fit in a uint. -int highestPowerBelowUintMax(uint x) pure nothrow +int highestPowerBelowUintMax(uint x) pure nothrow @safe { assert(x>1); static immutable ubyte [22] maxpwr = [ 31, 20, 15, 13, 12, 11, 10, 10, 9, 9, @@ -1080,7 +1096,7 @@ int highestPowerBelowUintMax(uint x) pure nothrow } // returns the maximum power of x that will fit in a ulong. -int highestPowerBelowUlongMax(uint x) pure nothrow +int highestPowerBelowUlongMax(uint x) pure nothrow @safe { assert(x>1); static immutable ubyte [39] maxpwr = [ 63, 40, 31, 27, 24, 22, 21, 20, 19, 18, @@ -1100,21 +1116,26 @@ int highestPowerBelowUlongMax(uint x) pure nothrow return 2; } -version(unittest) { +version(unittest) +{ -int slowHighestPowerBelowUintMax(uint x) pure nothrow +int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe { int pwr = 1; - for (ulong q = x;x*q < cast(ulong)uint.max; ) { + for (ulong q = x;x*q < cast(ulong)uint.max; ) + { q*=x; ++pwr; } return pwr; } -unittest +@safe pure unittest { - assert(highestPowerBelowUintMax(10)==9); - for (int k=82; k<88; ++k) {assert(highestPowerBelowUintMax(k)== slowHighestPowerBelowUintMax(k)); } + assert(highestPowerBelowUintMax(10)==9); + for (int k=82; k<88; ++k) + { + assert(highestPowerBelowUintMax(k)== slowHighestPowerBelowUintMax(k)); + } } } @@ -1219,7 +1240,11 @@ BigDigit [] addInt(const BigDigit[] x, ulong y) pure nothrow if (x.length < 2 && hi!=0) ++len; BigDigit [] result = new BigDigit[len+1]; result[0..x.length] = x[]; - if (x.length < 2 && hi!=0) { result[1]=hi; hi=0; } + if (x.length < 2 && hi!=0) + { + result[1]=hi; + hi=0; + } uint carry = multibyteIncrementAssign!('+')(result[0..$-1], lo); if (hi!=0) carry += multibyteIncrementAssign!('+')(result[1..$-1], hi); if (carry) @@ -1396,7 +1421,8 @@ void squareInternal(BigDigit[] result, const BigDigit[] x) pure nothrow assert(result.length == 2*x.length); if (x.length <= KARATSUBASQUARELIMIT) { - if (x.length==1) { + if (x.length==1) + { result[1] = multibyteMul(result[0..1], x, x[0], 0); return; } @@ -1458,7 +1484,7 @@ void divModInternal(BigDigit [] quotient, BigDigit[] remainder, const BigDigit [ delete vn; } -unittest +pure unittest { immutable(uint) [] u = [0, 0xFFFF_FFFE, 0x8000_0000]; immutable(uint) [] v = [0xFFFF_FFFF, 0x8000_0000]; @@ -1481,7 +1507,7 @@ private: // buff.length must be data.length*8 if separator is zero, // or data.length*9 if separator is non-zero. It will be completely filled. char [] biguintToHex(char [] buff, const BigDigit [] data, char separator=0) - pure + pure @safe { int x=0; for (ptrdiff_t i=data.length - 1; i>=0; --i) @@ -1758,7 +1784,8 @@ body /* result = result - right * Returns carry = 1 if result was less than right. */ -BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right) pure nothrow +BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right) +pure nothrow { assert(result.length >= right.length); uint c = multibyteSub(result[0..right.length], result[0..right.length], right, 0); @@ -1769,7 +1796,8 @@ BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right) pure noth /* result = result + right */ -BigDigit addAssignSimple(BigDigit [] result, const(BigDigit) [] right) pure nothrow +BigDigit addAssignSimple(BigDigit [] result, const(BigDigit) [] right) +pure nothrow { assert(result.length >= right.length); uint c = multibyteAdd(result[0..right.length], result[0..right.length], right, 0); @@ -1846,7 +1874,7 @@ bool inplaceSub(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y) /* Determine how much space is required for the temporaries * when performing a Karatsuba multiplication. */ -size_t karatsubaRequiredBuffSize(size_t xlen) pure nothrow +size_t karatsubaRequiredBuffSize(size_t xlen) pure nothrow @safe { return xlen <= KARATSUBALIMIT ? 0 : 2*xlen; // - KARATSUBALIMIT+2; } @@ -2053,7 +2081,7 @@ void schoolbookDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) { // Note: On DMD, this is only ~10% faster than the non-asm code. uint *p = &u[j + v.length - 1]; - asm + asm pure nothrow { mov EAX, p; mov EDX, [EAX+4]; @@ -2107,7 +2135,8 @@ again: private: // TODO: Replace with a library call -void itoaZeroPadded(char[] output, uint value, int radix = 10) pure nothrow +void itoaZeroPadded(char[] output, uint value, int radix = 10) + pure nothrow @safe { ptrdiff_t x = output.length - 1; for( ; x >= 0; --x) @@ -2117,7 +2146,7 @@ void itoaZeroPadded(char[] output, uint value, int radix = 10) pure nothrow } } -void toHexZeroPadded(char[] output, uint value) pure nothrow +void toHexZeroPadded(char[] output, uint value) pure nothrow @safe { ptrdiff_t x = output.length - 1; static immutable string hexDigits = "0123456789ABCDEF"; @@ -2133,7 +2162,7 @@ private: // Returns the highest value of i for which left[i]!=right[i], // or 0 if left[] == right[] size_t highestDifferentDigit(const BigDigit [] left, const BigDigit [] right) -pure nothrow +pure nothrow @safe { assert(left.length == right.length); for (ptrdiff_t i = left.length - 1; i>0; --i) @@ -2145,7 +2174,7 @@ pure nothrow } // Returns the lowest value of i for which x[i]!=0. -int firstNonZeroDigit(const BigDigit [] x) pure nothrow +int firstNonZeroDigit(const BigDigit [] x) pure nothrow @safe { int k = 0; while (x[k]==0) @@ -2181,7 +2210,8 @@ Returns: Max-Planck Institute fuer Informatik, (Oct 1998). */ void recursiveDivMod(BigDigit[] quotient, BigDigit[] u, const(BigDigit)[] v, - BigDigit[] scratch, bool mayOverflow = false) pure nothrow + BigDigit[] scratch, bool mayOverflow = false) + pure nothrow in { // v must be normalized @@ -2324,7 +2354,7 @@ pure nothrow version(unittest) { - import std.c.stdio; + import core.stdc.stdio; } unittest diff --git a/std/internal/math/biguintnoasm.d b/std/internal/math/biguintnoasm.d index 8d9a34bdbb3..035aad4b998 100644 --- a/std/internal/math/biguintnoasm.d +++ b/std/internal/math/biguintnoasm.d @@ -35,7 +35,7 @@ enum : int { KARATSUBASQUARELIMIT=12 }; // Minimum value for which square Karats * Set op == '+' for addition, '-' for subtraction. */ uint multibyteAddSub(char op)(uint[] dest, const(uint) [] src1, - const (uint) [] src2, uint carry) pure @nogc + const (uint) [] src2, uint carry) pure @nogc @safe { ulong c = carry; for (size_t i = 0; i < src2.length; ++i) @@ -99,7 +99,8 @@ unittest * op must be '+' or '-' * Returns final carry or borrow (0 or 1) */ -uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) pure @nogc +uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) + pure @nogc @safe { static if (op=='+') { @@ -137,7 +138,8 @@ uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) pure @nogc /** dest[] = src[] << numbits * numbits must be in the range 1..31 */ -uint multibyteShl(uint [] dest, const(uint) [] src, uint numbits) pure @nogc +uint multibyteShl(uint [] dest, const(uint) [] src, uint numbits) + pure @nogc @safe { ulong c = 0; for (size_t i = 0; i < dest.length; ++i) @@ -153,7 +155,8 @@ uint multibyteShl(uint [] dest, const(uint) [] src, uint numbits) pure @nogc /** dest[] = src[] >> numbits * numbits must be in the range 1..31 */ -void multibyteShr(uint [] dest, const(uint) [] src, uint numbits) pure @nogc +void multibyteShr(uint [] dest, const(uint) [] src, uint numbits) + pure @nogc @safe { ulong c = 0; for(ptrdiff_t i = dest.length; i!=0; --i) @@ -188,7 +191,7 @@ unittest * Returns carry. */ uint multibyteMul(uint[] dest, const(uint)[] src, uint multiplier, uint carry) - pure @nogc + pure @nogc @safe { assert(dest.length == src.length); ulong c = carry; @@ -215,7 +218,7 @@ unittest * Returns carry out of MSB (0..FFFF_FFFF). */ uint multibyteMulAdd(char op)(uint [] dest, const(uint)[] src, - uint multiplier, uint carry) pure @nogc + uint multiplier, uint carry) pure @nogc @safe { assert(dest.length == src.length); ulong c = carry; @@ -266,7 +269,7 @@ unittest ---- */ void multibyteMultiplyAccumulate(uint [] dest, const(uint)[] left, const(uint) - [] right) pure @nogc + [] right) pure @nogc @safe { for (size_t i = 0; i < right.length; ++i) { @@ -278,7 +281,8 @@ void multibyteMultiplyAccumulate(uint [] dest, const(uint)[] left, const(uint) /** dest[] /= divisor. * overflow is the initial remainder, and must be in the range 0..divisor-1. */ -uint multibyteDivAssign(uint [] dest, uint divisor, uint overflow) pure @nogc +uint multibyteDivAssign(uint [] dest, uint divisor, uint overflow) + pure @nogc @safe { ulong c = cast(ulong)overflow; for(ptrdiff_t i = dest.length-1; i>= 0; --i) @@ -306,7 +310,8 @@ unittest } // Set dest[2*i..2*i+1]+=src[i]*src[i] -void multibyteAddDiagonalSquares(uint[] dest, const(uint)[] src) pure @nogc +void multibyteAddDiagonalSquares(uint[] dest, const(uint)[] src) + pure @nogc @safe { ulong c = 0; for(size_t i = 0; i < src.length; ++i) @@ -321,7 +326,8 @@ void multibyteAddDiagonalSquares(uint[] dest, const(uint)[] src) pure @nogc } // Does half a square multiply. (square = diagonal + 2*triangle) -void multibyteTriangleAccumulate(uint[] dest, const(uint)[] x) pure @nogc +void multibyteTriangleAccumulate(uint[] dest, const(uint)[] x) + pure @nogc @safe { // x[0]*x[1...$] + x[1]*x[2..$] + ... + x[$-2]x[$-1..$] dest[x.length] = multibyteMul(dest[1 .. x.length], x[1..$], x[0], 0); @@ -354,7 +360,7 @@ void multibyteTriangleAccumulate(uint[] dest, const(uint)[] x) pure @nogc dest[2*x.length-2] = cast(uint)c; } -void multibyteSquare(BigDigit[] result, const(BigDigit) [] x) pure @nogc +void multibyteSquare(BigDigit[] result, const(BigDigit) [] x) pure @nogc @safe { multibyteTriangleAccumulate(result, x); result[$-1] = multibyteShl(result[1..$-1], result[1..$-1], 1); // mul by 2 diff --git a/std/internal/math/biguintx86.d b/std/internal/math/biguintx86.d index bbec4965bb7..31f2b90c6b8 100644 --- a/std/internal/math/biguintx86.d +++ b/std/internal/math/biguintx86.d @@ -69,7 +69,7 @@ private: * Each instance of '@' in s is replaced by 0,1,...n-1. This is a helper * function for some of the asm routines. */ -string indexedLoopUnroll(int n, string s) pure +string indexedLoopUnroll(int n, string s) pure @safe { string u; for (int i = 0; i> numbits @@ -305,8 +305,7 @@ uint multibyteShl(uint [] dest, const uint [] src, uint numbits) pure // Timing: // K7 1.2/int. PM 1.7/int P4 5.3/int enum { LASTPARAM = 4*4 } // 3* pushes + return address. - - asm { + asm pure nothrow { naked; push ESI; push EDI; @@ -381,13 +380,13 @@ L_length1: psllq MM1, MM3; movd [EDI], MM1; jmp L_alldone; - } + } } void multibyteShr(uint [] dest, const uint [] src, uint numbits) pure { enum { LASTPARAM = 4*4 } // 3* pushes + return address. - asm { + asm pure nothrow { naked; push ESI; push EDI; @@ -465,7 +464,7 @@ L_length1: movd [EDI +4*EBX], MM1; jmp L_alldone; - } + } } /** dest[#] = src[#] >> numbits @@ -477,7 +476,7 @@ void multibyteShrNoMMX(uint [] dest, const uint [] src, uint numbits) pure // 2.0 cycles/int on PPro..PM (limited by execution port p0) // Terrible performance on AMD64, which has 7 cycles for SHRD!! enum { LASTPARAM = 4*4 } // 3* pushes + return address. - asm { + asm pure nothrow { naked; push ESI; push EDI; @@ -515,7 +514,7 @@ L_last: pop EDI; pop ESI; ret 4*4; - } + } } unittest @@ -577,7 +576,7 @@ uint multibyteMul(uint[] dest, const uint[] src, uint multiplier, uint carry) { __gshared int zero = 0; } - asm { + asm pure nothrow { naked; push ESI; push EDI; @@ -617,7 +616,7 @@ L_odd: pop EDI; pop ESI; ret 5*4; - } + } } unittest @@ -653,8 +652,8 @@ string asmMulAdd_innerloop(string OP, string M_ADDRESS) pure { // The first member of 'dest' which will be modified is [EDI+4*EBX]. // EAX must already contain the first member of 'src', [ESI+4*EBX]. -version(D_PIC) { bool using_PIC = true; } else { bool using_PIC=false; } -return "asm { + version(D_PIC) { bool using_PIC = true; } else { bool using_PIC = false; } + return " // Entry point for even length add EBX, 1; mov EBP, ECX; // carry @@ -683,9 +682,9 @@ L1: mov ECX, zero; mov EAX, [ESI+4*EBX]; adc ECX, EDX; - " ~ - (using_PIC ? "" : " mov storagenop, EDX; ") // make #uops in loop a multiple of 3, can't do this in PIC mode. - ~ " +" ~ + (using_PIC ? "" : " mov storagenop, EDX; ") // make #uops in loop a multiple of 3, can't do this in PIC mode. +~ " mul int ptr [" ~ M_ADDRESS ~ "]; " ~ OP ~ " [-4+EDI+4*EBX], EBP; mov EBP, zero; @@ -698,12 +697,13 @@ L1: jl L1; L_done: " ~ OP ~ " [-8+EDI+4*EBX], ECX; adc EBP, 0; - }"; +"; // final carry is now in EBP } -string asmMulAdd_enter_odd(string OP, string M_ADDRESS) pure { -return "asm { +string asmMulAdd_enter_odd(string OP, string M_ADDRESS) pure +{ + return " mul int ptr [" ~M_ADDRESS ~"]; mov EBP, zero; add ECX, EAX; @@ -713,7 +713,7 @@ return "asm { add EBX, 2; jl L1; jmp L_done; - }"; +"; } @@ -751,7 +751,7 @@ uint multibyteMulAdd(char op)(uint [] dest, const uint [] src, uint } enum { LASTPARAM = 5*4 } // 4* pushes + return address. - asm { + asm pure nothrow { naked; push ESI; @@ -771,19 +771,19 @@ uint multibyteMulAdd(char op)(uint [] dest, const uint [] src, uint mov EAX, [ESI+4*EBX]; test EBX, 1; jnz L_enter_odd; -} - // Main loop, with entry point for even length -mixin(asmMulAdd_innerloop(OP, "ESP+LASTPARAM")); -asm { + } + // Main loop, with entry point for even length + mixin("asm pure nothrow {" ~ asmMulAdd_innerloop(OP, "ESP+LASTPARAM") ~ "}"); + asm pure nothrow { mov EAX, EBP; // get final carry pop EBP; pop EBX; pop EDI; pop ESI; ret 5*4; -} + } L_enter_odd: - mixin(asmMulAdd_enter_odd(OP, "ESP+LASTPARAM")); + mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd(OP, "ESP+LASTPARAM") ~ "}"); } unittest @@ -831,7 +831,7 @@ void multibyteMultiplyAccumulate(uint [] dest, const uint[] left, } enum { LASTPARAM = 6*4 } // 4* pushes + local + return address. - asm { + asm pure nothrow { naked; push ESI; @@ -860,10 +860,10 @@ outer_loop: mov EAX, [ESI+4*EBX]; test EBX, 1; jnz L_enter_odd; - } - // -- Inner loop, with even entry point - mixin(asmMulAdd_innerloop("add", "ESP")); -asm { + } + // -- Inner loop, with even entry point + mixin("asm pure nothrow { " ~ asmMulAdd_innerloop("add", "ESP") ~ "}"); + asm pure nothrow { mov [-4+EDI+4*EBX], EBP; add EDI, 4; cmp EDI, [ESP + LASTPARAM + 4*0]; // is EDI = &dest[$]? @@ -881,9 +881,9 @@ outer_done: pop EDI; pop ESI; ret 6*4; -} + } L_enter_odd: - mixin(asmMulAdd_enter_odd("add", "ESP")); + mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd("add", "ESP") ~ "}"); } /** dest[#] /= divisor. @@ -909,7 +909,7 @@ uint multibyteDivAssign(uint [] dest, uint divisor, uint overflow) pure // [ESP] = kinv (2^64 /divisor) enum { LASTPARAM = 5*4 } // 4* pushes + return address. enum { LOCALS = 2*4} // MASK, KINV - asm { + asm pure nothrow { naked; push ESI; @@ -1024,35 +1024,35 @@ void multibyteAddDiagonalSquares(uint [] dest, const uint [] src) pure improve it by moving the mov EAX after the adc [EDI], EAX. Probably not worthwhile. */ enum { LASTPARAM = 4*5 } // 4* pushes + return address. - asm { - naked; - push ESI; - push EDI; - push EBX; - push ECX; - mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr; - mov EBX, [ESP + LASTPARAM + 4*0]; //src.length; - mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr; - lea EDI, [EDI + 8*EBX]; // EDI = end of dest - lea ESI, [ESI + 4*EBX]; // ESI = end of src - neg EBX; // count UP to zero. - xor ECX, ECX; // initial carry = 0. + asm pure nothrow { + naked; + push ESI; + push EDI; + push EBX; + push ECX; + mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr; + mov EBX, [ESP + LASTPARAM + 4*0]; //src.length; + mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr; + lea EDI, [EDI + 8*EBX]; // EDI = end of dest + lea ESI, [ESI + 4*EBX]; // ESI = end of src + neg EBX; // count UP to zero. + xor ECX, ECX; // initial carry = 0. L1: - mov EAX, [ESI + 4*EBX]; - mul EAX, EAX; - shr CL, 1; // get carry - adc [EDI + 8*EBX], EAX; - adc [EDI + 8*EBX + 4], EDX; - setc CL; // save carry - inc EBX; - jnz L1; + mov EAX, [ESI + 4*EBX]; + mul EAX, EAX; + shr CL, 1; // get carry + adc [EDI + 8*EBX], EAX; + adc [EDI + 8*EBX + 4], EDX; + setc CL; // save carry + inc EBX; + jnz L1; pop ECX; pop EBX; pop EDI; pop ESI; ret 4*4; - } + } } unittest @@ -1111,7 +1111,7 @@ void multibyteTriangleAccumulateAsm(uint[] dest, const uint[] src) pure } enum { LASTPARAM = 6*4 } // 4* pushes + local + return address. - asm { + asm pure nothrow { naked; push ESI; @@ -1155,8 +1155,8 @@ outer_loop: jnz L_enter_odd; } // -- Inner loop, with even entry point - mixin(asmMulAdd_innerloop("add", "ESP")); - asm { + mixin("asm pure nothrow { " ~ asmMulAdd_innerloop("add", "ESP") ~ "}"); + asm pure nothrow { mov [-4+EDI+4*EBX], EBP; add EDI, 4; cmp EDI, [ESP + LASTPARAM + 4*2]; // is EDI = &dest[$-3]? @@ -1191,8 +1191,8 @@ length_is_3: pop ESI; ret 4*4; } - L_enter_odd: - mixin(asmMulAdd_enter_odd("add", "ESP")); +L_enter_odd: + mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd("add", "ESP") ~ "}"); } unittest diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d index 28aafce93e1..a05f6e2780e 100644 --- a/std/internal/math/gammafunction.d +++ b/std/internal/math/gammafunction.d @@ -1422,22 +1422,26 @@ assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109) < 0.0000000000 } +// DAC: These values are Bn / n for n=2,4,6,8,10,12,14. +immutable real [7] Bn_n = [ + 1.0L/(6*2), -1.0L/(30*4), 1.0L/(42*6), -1.0L/(30*8), + 5.0L/(66*10), -691.0L/(2730*12), 7.0L/(6*14) ]; + /** Digamma function * * The digamma function is the logarithmic derivative of the gamma function. * * digamma(x) = d/dx logGamma(x) * +* References: +* 1. Abramowitz, M., and Stegun, I. A. (1970). +* Handbook of mathematical functions. Dover, New York, +* pages 258-259, equations 6.3.6 and 6.3.18. */ real digamma(real x) { // Based on CEPHES, Stephen L. Moshier. - // DAC: These values are Bn / n for n=2,4,6,8,10,12,14. - static immutable real [7] Bn_n = [ - 1.0L/(6*2), -1.0L/(30*4), 1.0L/(42*6), -1.0L/(30*8), - 5.0L/(66*10), -691.0L/(2730*12), 7.0L/(6*14) ]; - real p, q, nz, s, w, y, z; long i, n; int negative; @@ -1503,13 +1507,12 @@ done: return y; } -version(unittest) import core.stdc.stdio; unittest { // Exact values - assert(digamma(1)== -EULERGAMMA); + assert(digamma(1.0)== -EULERGAMMA); assert(feqrel(digamma(0.25), -PI/2 - 3* LN2 - EULERGAMMA) >= real.mant_dig-7); assert(feqrel(digamma(1.0L/6), -PI/2 *sqrt(3.0L) - 2* LN2 -1.5*log(3.0L) - EULERGAMMA) >= real.mant_dig-7); - assert(digamma(-5).isNaN); + assert(digamma(-5.0).isNaN()); assert(feqrel(digamma(2.5), -EULERGAMMA - 2*LN2 + 2.0 + 2.0L/3) >= real.mant_dig-9); assert(isIdentical(digamma(NaN(0xABC)), NaN(0xABC))); @@ -1518,6 +1521,53 @@ unittest { for (int u=k; u>=1; --u) { y += 1.0L/u; } - assert(feqrel(digamma(k+1), -EULERGAMMA + y) >= real.mant_dig-2); + assert(feqrel(digamma(k+1.0), -EULERGAMMA + y) >= real.mant_dig-2); + } +} + +/** Log Minus Digamma function +* +* logmdigamma(x) = log(x) - digamma(x) +* +* References: +* 1. Abramowitz, M., and Stegun, I. A. (1970). +* Handbook of mathematical functions. Dover, New York, +* pages 258-259, equations 6.3.6 and 6.3.18. +*/ +real logmdigamma(real x) +{ + if (x <= 0.0) + { + if (x == 0.0) + { + return real.infinity; + } + return real.nan; + } + + real s = x; + real w = 0.0; + while ( s < 10.0 ) { + w += 1.0/s; + s += 1.0; } + + real y; + if ( s < 1.0e17 ) { + immutable real z = 1.0/(s * s); + y = z * poly(z, Bn_n); + } else + y = 0.0; + + return x == s ? y + 0.5L/s : (log(x/s) + 0.5L/s + y + w); +} + +unittest { + assert(logmdigamma(-5.0).isNaN()); + assert(isIdentical(logmdigamma(NaN(0xABC)), NaN(0xABC))); + assert(logmdigamma(0.0) == real.infinity); + for(auto x = 0.01; x < 1.0; x += 0.1) + assert(approxEqual(digamma(x), log(x) - logmdigamma(x))); + for(auto x = 1.0; x < 15.0; x += 1.0) + assert(approxEqual(digamma(x), log(x) - logmdigamma(x))); } diff --git a/std/internal/scopebuffer.d b/std/internal/scopebuffer.d index 3e50e820f5b..67323e25b85 100644 --- a/std/internal/scopebuffer.d +++ b/std/internal/scopebuffer.d @@ -10,7 +10,6 @@ module std.internal.scopebuffer; //debug=ScopeBuffer; -private import core.exception; private import core.stdc.stdlib : realloc; private import std.traits; @@ -100,6 +99,8 @@ struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) !hasElaborateAssign!T) { import core.stdc.string : memcpy; + import core.exception : onOutOfMemoryError; + /************************** * Initialize with buf to use as scratch buffer space. @@ -272,7 +273,7 @@ struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) newsize |= wasResized; void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof); if (!newBuf) - core.exception.onOutOfMemoryError(); + onOutOfMemoryError(); if (!(bufLen & wasResized)) { memcpy(newBuf, buf, used * T.sizeof); @@ -352,7 +353,6 @@ unittest assert(s == "hellobettyeven more"); } - // const unittest { @@ -366,7 +366,6 @@ unittest const slice1 = csb[]; } - /********************************* * This is a slightly simpler way to create a ScopeBuffer instance * that uses type deduction. diff --git a/std/internal/test/dummyrange.d b/std/internal/test/dummyrange.d new file mode 100644 index 00000000000..a9e7891de3c --- /dev/null +++ b/std/internal/test/dummyrange.d @@ -0,0 +1,241 @@ +/** +For testing only. +Used with the dummy ranges for testing higher order ranges. +*/ +module std.internal.test.dummyrange; + +import std.typecons; +import std.typetuple; +import std.range.primitives; + +enum RangeType +{ + Input, + Forward, + Bidirectional, + Random +} + +enum Length +{ + Yes, + No +} + +enum ReturnBy +{ + Reference, + Value +} + +// Range that's useful for testing other higher order ranges, +// can be parametrized with attributes. It just dumbs down an array of +// numbers 1..10. +struct DummyRange(ReturnBy _r, Length _l, RangeType _rt) +{ + // These enums are so that the template params are visible outside + // this instantiation. + enum r = _r; + enum l = _l; + enum rt = _rt; + + uint[] arr = [1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]; + + void reinit() + { + // Workaround for DMD bug 4378 + arr = [1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]; + } + + void popFront() + { + arr = arr[1..$]; + } + + @property bool empty() const + { + return arr.length == 0; + } + + static if(r == ReturnBy.Reference) + { + @property ref inout(uint) front() inout + { + return arr[0]; + } + + @property void front(uint val) + { + arr[0] = val; + } + } + else + { + @property uint front() const + { + return arr[0]; + } + } + + static if(rt >= RangeType.Forward) + { + @property typeof(this) save() + { + return this; + } + } + + static if(rt >= RangeType.Bidirectional) + { + void popBack() + { + arr = arr[0..$ - 1]; + } + + static if(r == ReturnBy.Reference) + { + @property ref inout(uint) back() inout + { + return arr[$ - 1]; + } + + @property void back(uint val) + { + arr[$ - 1] = val; + } + + } + else + { + @property uint back() const + { + return arr[$ - 1]; + } + } + } + + static if(rt >= RangeType.Random) + { + static if(r == ReturnBy.Reference) + { + ref inout(uint) opIndex(size_t index) inout + { + return arr[index]; + } + + void opIndexAssign(uint val, size_t index) + { + arr[index] = val; + } + } + else + { + uint opIndex(size_t index) const + { + return arr[index]; + } + } + + typeof(this) opSlice(size_t lower, size_t upper) + { + auto ret = this; + ret.arr = arr[lower..upper]; + return ret; + } + } + + static if(l == Length.Yes) + { + @property size_t length() const + { + return arr.length; + } + + alias opDollar = length; + } +} + +enum dummyLength = 10; + +alias AllDummyRanges = TypeTuple!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Input), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional) +); + +/** +Tests whether forward, bidirectional and random access properties are +propagated properly from the base range(s) R to the higher order range +H. Useful in combination with DummyRange for testing several higher +order ranges. +*/ +template propagatesRangeType(H, R...) +{ + static if(allSatisfy!(isRandomAccessRange, R)) + enum bool propagatesRangeType = isRandomAccessRange!H; + else static if(allSatisfy!(isBidirectionalRange, R)) + enum bool propagatesRangeType = isBidirectionalRange!H; + else static if(allSatisfy!(isForwardRange, R)) + enum bool propagatesRangeType = isForwardRange!H; + else + enum bool propagatesRangeType = isInputRange!H; +} + +template propagatesLength(H, R...) +{ + static if(allSatisfy!(hasLength, R)) + enum bool propagatesLength = hasLength!H; + else + enum bool propagatesLength = !hasLength!H; +} + +/** +Reference type input range +*/ +class ReferenceInputRange(T) +{ + import std.array : array; + + this(Range)(Range r) if (isInputRange!Range) {_payload = array(r);} + final @property ref T front(){return _payload.front;} + final void popFront(){_payload.popFront();} + final @property bool empty(){return _payload.empty;} + protected T[] _payload; +} + +/** +Reference forward range +*/ +class ReferenceForwardRange(T) : ReferenceInputRange!T +{ + this(Range)(Range r) if (isInputRange!Range) {super(r);} + final @property ReferenceForwardRange save() + {return new ReferenceForwardRange!T(_payload);} +} + +//Infinite input range +class ReferenceInfiniteInputRange(T) +{ + this(T first = T.init) {_val = first;} + final @property T front(){return _val;} + final void popFront(){++_val;} + enum bool empty = false; + protected T _val; +} + +//Infinite forward range +class ReferenceInfiniteForwardRange(T) : ReferenceInfiniteInputRange!T +{ + this(T first = T.init) {super(first);} + final @property ReferenceInfiniteForwardRange save() + {return new ReferenceInfiniteForwardRange!T(_val);} +} diff --git a/std/internal/unicode_comp.d b/std/internal/unicode_comp.d index 8b4cf344fcf..966a83e0355 100644 --- a/std/internal/unicode_comp.d +++ b/std/internal/unicode_comp.d @@ -1,6 +1,8 @@ module std.internal.unicode_comp; import std.internal.unicode_tables; +@safe pure nothrow @nogc: + static if(size_t.sizeof == 8) { //6976 bytes enum combiningClassTrieEntries = TrieEntry!(ubyte, 8, 7, 6)([ 0x0, 0x20, 0x120], [ 0x100, 0x400, 0x1240], [ 0x402030202020100, 0x206020202020205, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, 0x0, 0x20001, 0x300000000, 0x5000400000000, 0x8000000070006, 0xb0000000a0009, 0xe0000000d000c, 0x11000f0010000f, 0x11000f0011000f, 0x1100000011000f, 0x11000f00120000, 0x13000000110000, 0x17001600150014, 0x1b001a00190018, 0x1d0000001c, 0x0, 0x0, 0x1e0000, 0x0, 0x0, 0x0, 0x2000000000001f, 0x2100000000, 0x22, 0x240023, 0x28002700260025, 0x2a000000000029, 0x2b000000000000, 0x0, 0x0, 0x2c000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2d000000000000, 0x2f0000002e0000, 0x0, 0x0, 0x3100000030, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x34003300320000, 0x0, 0x36000000000035, 0x3a003900380037, 0x3c003b00000000, 0x3d000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3e, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x40000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4200350000, 0x3a000000000043, 0x0, 0x0, 0x0, 0x0, 0x4400000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4600450000, 0x470000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, 0xdcdce8e6e6e6e6e6, 0xdcdcdcdcd8e8dcdc, 0xcadcdcdcdccacadc, 0xdcdcdcdcdcdcdcca, 0x1010101dcdcdcdc, 0xe6e6e6dcdcdcdc01, 0xdce6f0e6e6e6e6e6, 0xdcdce6e6e6dcdc, 0xe6dcdcdcdce6e6e6, 0xe9eaeae9e6dcdce8, 0xe6e6e6e6e6e9eaea, 0xe6e6e6e6e6e6e6e6, 0x0, 0x0, 0xe6e6e6e6e6000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6dce6e6e6e6dc00, 0xe6e6e6e6dcdee6e6, 0xdcdcdcdcdcdce6e6, 0xe6e4dee6e6dce6e6, 0x11100f0e0d0c0b0a, 0x1700161514131312, 0x1200dce600191800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6e6, 0x201f1e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f1e1d1c1b000000, 0xe6dcdce6e6222120, 0xdce6e6dce6e6e6e6, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0xe6e6000000000000, 0xe60000e6e6e6e6e6, 0xe60000e6dce6e6e6, 0xdce6e6dc00e6, 0x0, 0x0, 0x0, 0x0, 0x2400, 0x0, 0x0, 0x0, 0xdce6e6dce6e6dce6, 0xe6dce6dcdce6dcdc, 0xe6dce6dce6dce6e6, 0xe6e6dc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6e6000000, 0xe6dce6e6, 0x0, 0x0, 0x0, 0xe6e6000000000000, 0xe6e6e6e6e600e6e6, 0xe6e6e600e6e6e6e6, 0xe6e6e6e6e600, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdcdcdc00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6dce6e600000000, 0xdcdcdce6e6e6dce6, 0xe6dce6e6e61d1c1b, 0xe6e6e6e6dcdce6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x700000000, 0x0, 0x90000000000, 0xe6e6dce600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000000000, 0x5b540000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x96767, 0x0, 0x6b6b6b6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7676, 0x0, 0x7a7a7a7a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdcdc, 0x0, 0x0, 0xdc00dc0000000000, 0xd800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8400828100, 0x828282820000, 0xe6e60009e6e60082, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdc000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x700000000000000, 0x90900, 0x0, 0xdc0000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e60000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x900000000, 0x0, 0x0, 0x0, 0x900000000, 0x0, 0x0, 0x0, 0x90000, 0xe60000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe400, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdce6de00, 0x0, 0x0, 0xe600000000000000, 0xdc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0xe6e6e60000000000, 0xdc0000e6e6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x700000000, 0x0, 0x900000000, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6dce6000000, 0xe6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9090000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7000000000000, 0x0, 0x9090000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x700000000000000, 0x0, 0x0, 0x0, 0xdcdcdc0100e6e6e6, 0xdcdcdcdce6e6dcdc, 0x1010101010100e6, 0xdc0000000001, 0xe600000000, 0x0, 0xe6e6e6e6e6dce6e6, 0xdcd6eae6e6dce6e6, 0xe6e6e6e6e6e6e6ca, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6, 0x0, 0x0, 0xdce6dce900000000, 0x0, 0x0, 0xe6e6e6e60101e6e6, 0xe6e6010101, 0xe60101000000e600, 0xdcdcdcdc0101e6dc, 0xe6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe600000000000000, 0xe6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x900000000000000, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0e0dee8e4da0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe600000000000000, 0xe6e6e6e600000000, 0xe6e6e6e6e6e6, 0x0, 0x0, 0x0, 0xe600000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6, 0x0, 0x9000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x900000000, 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, 0xe6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdcdcdc000000, 0x0, 0x0, 0x0, 0x0, 0x9000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7000000, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe60000dce6e600e6, 0xe6e60000000000e6, 0xe600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdc0000000000, 0x0, 0xe600dc0000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x900000000dc01e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70900, 0xe6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x909000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x709000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d8d80000000000, 0xd8d8e20000000101, 0xd8d8d8, 0xdcdcdcdcdc000000, 0xe6e6e60000dcdcdc, 0xdcdce6e6, 0x0, 0x0, 0x0, 0xe6e6e6e60000, 0x0, 0x0, 0xe6e6e60000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); diff --git a/std/internal/unicode_decomp.d b/std/internal/unicode_decomp.d index 44c21fe5d59..bfe665b7bbb 100644 --- a/std/internal/unicode_decomp.d +++ b/std/internal/unicode_decomp.d @@ -1,6 +1,8 @@ module std.internal.unicode_decomp; import std.internal.unicode_tables; +@safe pure nothrow @nogc: + static if(size_t.sizeof == 8) { //22656 bytes enum compatMappingTrieEntries = TrieEntry!(ushort, 8, 8, 5)([ 0x0, 0x20, 0x2a0], [ 0x100, 0xa00, 0x21c0], [ 0x402030202020100, 0x706020202020205, 0x802020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x3000200010000, 0x7000600050004, 0xa000900080000, 0xc000b, 0xf000e000d0000, 0x11001000000000, 0x15001400130012, 0x19001800170016, 0x1b001a00000000, 0x0, 0x1c, 0x1e0000001d0000, 0x1f00000000, 0x0, 0x0, 0x0, 0x0, 0x2100200000, 0x2200000000, 0x2400230000, 0x0, 0x2500000000, 0x2700000026, 0x2800000000, 0x2900000000, 0x2a00000000, 0x2b00000000, 0x2c0000, 0x2e002d0000, 0x3100300000002f, 0x330032, 0x340000, 0x35000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3800370036, 0x0, 0x0, 0x0, 0x3b003a00390000, 0x3d003c, 0x410040003f003e, 0x45004400430042, 0x49004800470046, 0x4d004c004b004a, 0x510050004f004e, 0x530052, 0x57005600550054, 0x5a00590058, 0x5e005d005c005b, 0x6100000060005f, 0x620000, 0x0, 0x63000000000000, 0x67006600650064, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x69000000000068, 0x6a00000000, 0x0, 0x0, 0x6b000000000000, 0x0, 0x6c000000000000, 0x0, 0x0, 0x6e00000000006d, 0x7200710070006f, 0x7500740073, 0x79007800770076, 0x7d007c007b007a, 0x80007f007e0000, 0x81, 0x85008400830082, 0x89008800870086, 0x8d008c008b008a, 0x910090008f008e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92000000000000, 0x93000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x97009600950094, 0x9b009a00990098, 0x9f009e009d009c, 0xa200a100a0, 0xa600a500a400a3, 0xaa00a900a800a7, 0xae00ad00ac00ab, 0xb200b100b000af, 0xb600b500b400b3, 0xba00b900b800b7, 0xbe00bd00bc00bb, 0xc200c100c000bf, 0xc600c500c400c3, 0xca00c900c800c7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcc00cb, 0xcd0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcf00ce00000000, 0xd100d00000, 0x0, 0x0, 0x0, 0x0, 0xd500d400d300d2, 0xd900d800d700d6, 0xdd00dc00db00da, 0xdf00d300d200de, 0xe200e100e000d5, 0xe500e400e300d9, 0xe900e800e700e6, 0xed00ec00eb00ea, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf100f000ef00ee, 0xf300f2, 0x0, 0x0, 0x0, 0x0, 0xf700f600f500f4, 0xf8, 0xfb00fa00f9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff00fe00fd00fc, 0x103010201010100, 0x107010601050104, 0x10b010a01090108, 0x10c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x69200000015, 0x9000000000000, 0x30f034300000000, 0x11b20003, 0x78703140048, 0x49403c603ce, 0x58605730570056d, 0x5f8000005b005a6, 0x6580631062e062b, 0x6f906ea06e706e4, 0x7a907a6078f0000, 0x7e307bf07ac, 0x8b708b408b10000, 0x95f08cb, 0x9c209af09ac09a9, 0xa47000009ec09e2, 0xab30a8c0a890a86, 0xb550b490b460b43, 0xc5e0c5b0c410000, 0xc980c740c61, 0xd6e0d6b0d680000, 0xe1b00000e0c0d82, 0x9c8058c09c50589, 0xa3b05ec0a0a05ce, 0xa4105f20a3e05ef, 0xa6e061a0a4405f5, 0xaa2064700000000, 0xab006550aad0652, 0xab9065e0ad00675, 0xb0106a00afb069a, 0xb0a06a90b0406a3, 0xb1606ba, 0xb4f06f00b4c06ed, 0xb6b070f0b5206f3, 0xb3706d8000006f6, 0xbae072e0b730717, 0x7500bcc07430000, 0x7400bcf07460bd9, 0x78c000000000bc9, 0x7950c4d079b0c3e, 0xed70c47, 0xc8e07d90c8307ce, 0xca207ed, 0xd1d08580d070842, 0xd2b086c0d0d0848, 0xd49088a0d320873, 0xd5d08a60d380879, 0xd54089d, 0xd7808c10d7108ba, 0xd9808e10d7f08c8, 0xdc4090d0d9b08e4, 0xe0f09620de9093f, 0x97f0e290979096e, 0x8400614060d0e2f, 0xcae07f9, 0x0, 0x0, 0x8f0000000000000, 0xda7, 0x0, 0x0, 0x0, 0x0, 0x7360a670613060c, 0x78307800bb9073d, 0x70309f305b70c32, 0x8e70ca507f00b5f, 0x8d20d8d08d60d9e, 0x8ce0d9108da0d89, 0x9e505a900000d85, 0xe630e5a09de05a2, 0xb0706a600000000, 0xccc08170ba80728, 0xecc0e7b0ccf081a, 0xa64061006090b76, 0xaf80697, 0x9ef05b30c3b0789, 0xe680e5d0e600e57, 0x9f905bd09f605ba, 0xabf06640abc0661, 0xb6507090b620706, 0xcab07f60ca807f3, 0xd13084e0d10084b, 0xda408ed0da108ea, 0xd5a08a30d460887, 0xb1f06c300000000, 0x0, 0x9db059f00000000, 0xc9b07e60ac9066e, 0xc9107dc0c7b07c6, 0xe1509680c9407df, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa11073e0e9a0b0d, 0xde10eb80eb60eb4, 0x695, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4b00240012000f, 0x270006, 0xb4108400a280e96, 0xecf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b00000004001a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xed5, 0x5400000000, 0x54600000000, 0x0, 0x7410ee8001c0003, 0xfb40f630f43, 0x103c101600000fed, 0x1185, 0x0, 0x0, 0x0, 0x0, 0x0, 0x101f0fbd00000000, 0x1175111910f5108f, 0x1213, 0x0, 0x0, 0x0, 0x0, 0x0, 0x120c117e00000000, 0x124b120311d5, 0x10161011116e10ea, 0x11ee123c101f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11f811f011ae, 0x10f00fad, 0x100d0000, 0x0, 0x12ad000012b612b0, 0x12a4000000000000, 0x0, 0x12d712c212ce, 0x0, 0x0, 0x12c80000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x130a0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12ef000012f812f2, 0x132d000000000000, 0x0, 0x131b13041310, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1333133000000000, 0x0, 0x0, 0x12fb12b90000, 0x0, 0x0, 0x0, 0x12ec12aa12e912a7, 0x12f512b300000000, 0x1339133600000000, 0x130112bf12fe12bc, 0x130712c500000000, 0x131512d1130d12cb, 0x133f133c00000000, 0x131812d4132a12e6, 0x132112dd131e12da, 0x132412e0, 0x132712e3, 0x0, 0x0, 0x1342000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13e913e600000000, 0x17ca13ec178f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x185b179213ef0000, 0x1811, 0x0, 0x18520000186d, 0x0, 0x0, 0x0, 0x186a000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18820000, 0x0, 0x188b0000, 0x188e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1879187618731870, 0x18881885187f187c, 0x0, 0x0, 0x189a000000000000, 0x189d, 0x0, 0x0, 0x0, 0x1897000018941891, 0x0, 0x0, 0x0, 0x0, 0x18ac000000000000, 0x18af00000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18a618a318a00000, 0x18a900000000, 0x0, 0x0, 0x18b80000000018bb, 0x18be, 0x0, 0x0, 0x0, 0x18b518b2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18c1, 0x0, 0x0, 0x0, 0x0, 0x18ca18c400000000, 0x18c7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18cd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18d0, 0x18da000000000000, 0x18d618d3000018dd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18e618e000000000, 0x18e3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18e900000000, 0x18f318ef18ec, 0x0, 0x0, 0x0, 0x0, 0x18f6000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18ff000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18fc18f9, 0x0, 0x0, 0x0, 0x1902, 0x0, 0x0, 0x0, 0x0, 0x1907000000000000, 0x0, 0x0, 0x190a0000, 0x190d00000000, 0x1910000000000000, 0x0, 0x1913, 0x0, 0x0, 0x19040000, 0x0, 0x1916000000000000, 0x1931193519190000, 0x1938193c, 0x0, 0x191c0000, 0x0, 0x0, 0x0, 0x1922000000000000, 0x0, 0x0, 0x19250000, 0x192800000000, 0x192b000000000000, 0x0, 0x192e, 0x0, 0x0, 0x191f0000, 0x0, 0x0, 0x193f00000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1942, 0x0, 0x1a3800000000, 0x1a3e00001a3b, 0x1a4400001a41, 0x1a4700000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a4a000000000000, 0x1a4d0000, 0x1a5600001a531a50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5d50e550568, 0x6870e75062905e6, 0x71a060706cf06ac, 0x77e07230734, 0x82c06af0e7e07a4, 0x6920770056b088d, 0x9371a590e840e82, 0xe8e0e8c0a7d0a2e, 0xb79000006020e90, 0xe8807870e7105d3, 0xba30cd31a5d1a5b, 0x86a0ea41a610a24, 0x10ee10ec10ea1a63, 0xa110ae0123e123c, 0x10ec10ea086a0a24, 0x123e123c11f0, 0x0, 0x0, 0x0, 0x1313, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe86000000000000, 0xe900e660e8a09a0, 0xe980e940e920ad9, 0x1a650ea00e9e0e9c, 0xed31a670ea20ed1, 0xeac0eaa0ea60ea8, 0xeba0eb20eb00eae, 0xec00ebe0e790ebc, 0x6110ec40ec21a5f, 0x116e0eca0ec80ec6, 0xa1305da0a0705cb, 0xa1905e00a1605dd, 0xa6b06170a4a05fb, 0xa7a06260a71061d, 0xa7706230a740620, 0xaa9064e0aa5064a, 0xad6067b0ad30678, 0xaef06840acc0671, 0xb1906bd0afe069d, 0xb1c06c00b2206c6, 0xb2806cc0b2506c9, 0xb5806fc0b6e0712, 0xbab072b0ba50725, 0xbd207490bb10731, 0xbdf07560bd5074c, 0xc1207720bdc0753, 0xc1807780c150775, 0xc4a07980c440792, 0xc50079e0c5307a1, 0xc7f07ca0c7707c2, 0xc8a07d50c8607d1, 0xcef08380cec0835, 0xd1608510d0a0845, 0xd20085b0d190854, 0xd3f08800d350876, 0xd3b087c0d2e086f, 0xd4e089a0d420883, 0xd6308ac0d5708a0, 0xdc1090a0d6008a9, 0xdc709100dca0913, 0xd7b08c40d7408bd, 0xdde09270ddb0924, 0xde6093c0de30939, 0xdec09420def0945, 0xe0109540df50948, 0xe18096b0e040957, 0xe3509850e2c097c, 0xd510b2b0e380988, 0xd3509a60e210df2, 0x0, 0x9e905ad09fc05c0, 0x9b2057609b6057a, 0x9ba057e09be0582, 0x9cf059309ff05c3, 0x9d7059b09cb058f, 0xa0305c709d30597, 0xab6065b0ac20667, 0xa9306380a9f0644, 0xa9b06400a8f0634, 0xac5066a0a97063c, 0xb68070c0b5c0700, 0xc9f07ea0cc50810, 0xc6407af0c6807b3, 0xc6c07b70c7007bb, 0xcb508000cc80813, 0xcbd08080cb107fc, 0xcc1080c0cb90804, 0xd9508de0dbe0907, 0xdaa08f30dae08f7, 0xdb208fb0db608ff, 0xe09095c0dba0903, 0xe1e09710e240974, 0xe120965, 0x0, 0x10c1109f10be109c, 0x10d310b110ca10a8, 0xf160ef40f130ef1, 0xf280f060f1f0efd, 0x110610fb110310f8, 0x110a10ff, 0xf540f490f510f46, 0xf580f4d, 0x1145112311421120, 0x11571135114e112c, 0xf8b0f690f880f66, 0xf9d0f7b0f940f72, 0x119f1190119c118d, 0x11a7119811a31194, 0xfd20fc30fcf0fc0, 0xfda0fcb0fd60fc7, 0x11e611db11e311d8, 0x11ea11df, 0xffe0ff30ffb0ff0, 0x10020ff7, 0x122d121e122a121b, 0x1235122612311222, 0x1025000010220000, 0x102d000010290000, 0x1277125512741252, 0x128912671280125e, 0x106410421061103f, 0x10761054106d104b, 0x10f510f2108f1088, 0x1175117211191112, 0x1203120011d511d2, 0x124b1244, 0x10c510a310dc10ba, 0x10d710b510ce10ac, 0xf1a0ef80f310f0f, 0xf2c0f0a0f230f01, 0x114911271160113e, 0x115b113911521130, 0xf8f0f6d0fa60f84, 0xfa10f7f0f980f76, 0x127b125912921270, 0x128d126b12841262, 0x10681046107f105d, 0x107a10581071104f, 0x10e7108b10961099, 0x10e310e000001092, 0xee80ee50eeb0eee, 0x2a1170002a0f35, 0x116b111500200051, 0x116711640000111c, 0xf630f600f430f40, 0x350031002d0faa, 0x118511811178117b, 0x118911ab00000000, 0xfb40fb10fb70fba, 0x440040003c0000, 0x1213120f12061209, 0x1217123911f511f2, 0x101610131019101c, 0x995001c0018100a, 0x129d124700000000, 0x129912960000124e, 0x103c10390fed0fea, 0x3900031083, 0x1000100010001, 0x1000100010001, 0x100010001, 0x0, 0x1a690000, 0x4e000000000000, 0x0, 0x0, 0x0, 0x2ff02fc02fa, 0x0, 0x1000000000000, 0x1a6f000000000000, 0x1a7e1a7b00001a72, 0x0, 0xc0000008f, 0x0, 0x563000000000000, 0x920560, 0x0, 0x0, 0x1a76000000000000, 0x0, 0x1000000000000, 0x0, 0x0, 0x0, 0x0, 0xae00305, 0x392038303740365, 0x1aad02f403b003a1, 0xb3b00a500a10544, 0x30f034303140305, 0x392038303740365, 0x1aad02f403b003a1, 0xa500a10544, 0xb4107870a7d0692, 0xa280b790b0d0e8c, 0x8400cd30b3b05d3, 0xba3, 0x0, 0x0, 0x83f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe4d05e309a2099e, 0xe770a220a1e0000, 0x6ac06020e500000, 0xe6d0b0d06ac06ac, 0xa28073406cf06cf, 0x786077e0000, 0x82c083b06af0000, 0x82c082c, 0x897088f0863, 0x77c0000060a, 0x5b0071a0000060a, 0xa7d000005e305d5, 0x7230000067e0629, 0x136a136213540787, 0x68000000ae0136f, 0x10060f3a10ec11ee, 0x1aab, 0xa7d0a2e05e60000, 0x73e0ae0, 0x0, 0x3ca03c103e203da, 0x498045903d20455, 0x3de04e703d604cf, 0x3be051104eb049c, 0x6de06d406d106cf, 0x91f091b091806b2, 0x950094d068206e1, 0x72305e605e30734, 0xb3d0b330b300ae0, 0xdd60dd20dcf086a, 0xdfd0dfa0b410b40, 0x5d30a2e09a00a28, 0x0, 0x0, 0x30d0000, 0x0, 0x0, 0x0, 0x1a8d1a8600000000, 0x0, 0x0, 0x0, 0x0, 0x1a9200000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a981a9b1a950000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1aa0, 0x1aa50000, 0x1aa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1ab200001aaf, 0x0, 0x1ac100001ab81ab5, 0x1ac4, 0x0, 0x0, 0x0, 0x1ac80000, 0x1ace000000001acb, 0x1ad10000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1ad700000556, 0x0, 0x0, 0x55b054a1ad40000, 0x1add1ada, 0x1ae31ae0, 0x1ae91ae6, 0x0, 0x1aef1aec, 0x1afb1af8, 0x1b011afe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b131b101b0d1b0a, 0x0, 0x0, 0x0, 0x0, 0x1b071b041af51af2, 0x0, 0x1b191b1600000000, 0x1b1f1b1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b371b350000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x365030f03430314, 0x3a1039203830374, 0x342032f031c03b0, 0x382037303640355, 0x3f703af03a00391, 0xe600e200d900a3, 0xf600f200ee00ea, 0xb100ac00a700fa, 0xc500c000bb00b6, 0xdd00d400cf00ca, 0x368035903460319, 0x3a4039503860377, 0x3450332031f03b3, 0x385037603670358, 0x3fa03b203a30394, 0x172016e016a0166, 0x182017e017a0176, 0x192018e018a0186, 0x1a2019e019a0196, 0x1b201ae01aa01a6, 0x1c201be01ba01b6, 0x5d5056801ca01c6, 0x67e062905e605e3, 0x60706cf06ac0687, 0x77e07230734071a, 0x82c083b06af07a4, 0x6b2056b088d085e, 0x60a095a06820770, 0xa2e09a009370692, 0xb0d06020ad90a7d, 0xa280b79073e0ae0, 0xcd307870b3b05d3, 0xba308400a1105d8, 0xb410de1086a0a24, 0x30506110695, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1abc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x552054f0542, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b2c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6b2073e, 0x0, 0x0, 0x0, 0x1b2f000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x227c000000000000, 0x0, 0x0, 0x0, 0x0, 0x26b0000000000000, 0x0, 0x0, 0x0, 0x1f091f011efb1ee9, 0x1f1b1f171f131f0d, 0x1f5f1f571f4b1f21, 0x1f871f771f6f1f67, 0x1fb91fa51f8b1f89, 0x1fcd1fc71fc51fc1, 0x1feb1fe91fdd1fdb, 0x204f20451ff71fef, 0x207f207d2079206f, 0x20b420ae20982087, 0x20ce20cc20ca20c4, 0x20f820f220dc20da, 0x210f2108210020fc, 0x212f212b21292113, 0x2141213921352131, 0x21972195218b214f, 0x21e521e321d921d7, 0x32521f121ed21e9, 0x2260222303292211, 0x227a2274226e2266, 0x228422822280227e, 0x230c230622e22286, 0x231623122310230e, 0x2334233023222318, 0x235c235a23562354, 0x2376237423622360, 0x238a238823862384, 0x23aa23a823a62394, 0x23ee23de23dc23bc, 0x241c240a23fa23f6, 0x2452244c2442243e, 0x245e245c245a2456, 0x247e247a246c246a, 0x248e248a24842482, 0x2498249624922490, 0x250e250c24f224e8, 0x2530252c25282512, 0x2558255425522534, 0x25742572255c255a, 0x2592258425822578, 0x25ba25aa25982596, 0x25de25c825c425c2, 0x260025fa25e825e0, 0x261a261826142608, 0x26262624261e261c, 0x263c263a2638262a, 0x2658264e264a2648, 0x26622660265c265a, 0x2670266826662664, 0x26862684267e267c, 0x2690268e268a2688, 0x26a0269c26982692, 0x26aa26a826a626a2, 0x26b226ae, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b4900000000, 0x1fd11fcf1fcd, 0x0, 0x0, 0x0, 0x0, 0x1b8100001b7e, 0x1b8700001b84, 0x1b8d00001b8a, 0x1b9300001b90, 0x1b9900001b96, 0x1b9f00001b9c, 0x1ba500001ba20000, 0x1ba80000, 0x0, 0x1bb100001bae1bab, 0x1bba1bb700001bb4, 0x1bc01bbd0000, 0x1bc91bc6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b7b, 0x87000000000000, 0x1bcc1bd30000008a, 0x0, 0x0, 0x0, 0x1c4300001c26, 0x1c9200001bf6, 0x1caf00001c9b, 0x1cca00001cbf, 0x1cdc00001ccf, 0x1ceb00001ce1, 0x1cf700001cf20000, 0x1c100000, 0x0, 0x1d3b00001d261d1d, 0x1d611d5700001d42, 0x1d7e1d760000, 0x1caa1da1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e44000000001c01, 0x1e571e521e4d, 0x1ca11e6000000000, 0x0, 0x0, 0x0, 0x0, 0x1a10194919440000, 0x19501a141a12194b, 0x1a181a1619571955, 0x1a201a1e1a1c1a1a, 0x19661961195c19a6, 0x196f196d196819b0, 0x198e198319811977, 0x1947199d19981993, 0x19de19dc19da19d8, 0x198c19e419e219e0, 0x19ee19ec19ea19e8, 0x19f619f419f21975, 0x19fe197f19fa19f8, 0x1a2219a419a219d4, 0x1a2a1a281a261a24, 0x1a3019a81a2e1a2c, 0x19ae19ac19aa1a32, 0x19b819b619b419b2, 0x19c019be19bc19ba, 0x19c819c619c419c2, 0x1a361a3419cc19ca, 0x1a0019d219d019ce, 0x1a081a061a041a02, 0x1a0e1a0c1a0a, 0x1f171ee900000000, 0x1efd1ef120471eef, 0x1ef71f0d23641ef3, 0x1f212051208c1eeb, 0x1e901e001d701ce, 0x20d020401fb01f2, 0x245023c02330225, 0x1db01d20257024e, 0x1ff01f601ed01e4, 0x237022902110208, 0x25b025202490240, 0x21e0216022e, 0x2a0026802700260, 0x284026402880274, 0x2c402b00290026c, 0x2a402ec02b802c0, 0x2d002b402bc02ac, 0x2d402e402c80298, 0x2a8029c0278028c, 0x29402e8027c02cc, 0x2e002dc028002d8, 0x23fe21e321112021, 0x0, 0x0, 0x41c04110406082e, 0x440043904320427, 0x475046e044e0447, 0x4850482047f047c, 0x19571950194b1944, 0x196f19681961195c, 0x1993198e19831977, 0x194d1946199d1998, 0x1963195e19591952, 0x198519791971196a, 0x199f199a19951990, 0x1974197c1988, 0x20471eef1f171ee9, 0x1f5f1eed1f611f19, 0x22e203291fcd1f0f, 0x204f25c822232286, 0x2240221b223b0325, 0x23ca255e231c2007, 0x2098236823e21fab, 0x22961fdf1f4925a2, 0x208a1f731f2b262c, 0x20fa1ef31efd1ef1, 0x20b220b81fc92001, 0x1fd725681f292390, 0x48e048b04882083, 0x4b704b404b10491, 0x4c304c004bd04ba, 0x4e404cc04c904c6, 0x4d604a3034e033b, 0x5290518050304f2, 0x34d033a0327053a, 0x7390a7f0a8206b4, 0x1c0a1bff1bf11bd8, 0x1c731c411c241c1a, 0x1cbd1cad1c991c90, 0x1cdf1cda1ccd1c1e, 0x1bde1cf51cf01bfb, 0x1c8e1d111d0f1ca6, 0x1d551d391d1b1d0d, 0x1ddc1c311d9f1d74, 0x1e041e001def1c22, 0x1c351e1b1e191e11, 0x1e421c5d1e341bed, 0x1e551e501e4b, 0x1beb1be51be01bda, 0x1c0c1c041bf91bf3, 0x1c331c201c1c1c13, 0x1c2e1c291c3c1c37, 0x1c501c571c4b1c46, 0x1c6d1c661c5f1c5c, 0x1c8b1c841c7d1c61, 0x1cb21ca81ca41c95, 0x1cd61cd21cc21cb7, 0x1c811d031cfa1ce4, 0x1d291d351d171d0c, 0x1d4c1d451d201d30, 0x1d6b1d641d3e1d51, 0x1d811d951d701d5a, 0x1d8f1d8a1d9b1d85, 0x1db81da41dac1d79, 0x1dc51dbf1dbb1db2, 0x1dd61dd21dce1dca, 0x1df11de61de31dde, 0x1e0b1e061c681df5, 0x1e291e241e1f1e13, 0x1c6f1e391e361e2e, 0x3610352033f0311, 0x39d038e037f0370, 0x33e032b03bb03ac, 0x37e036f03600351, 0x3ba03ab039c038d, 0x4230418040d0402, 0x56a0a530b0f042e, 0xa590ce60c580a0f, 0x210a06db0a600a5c, 0x223d21f920892200, 0xbea11b40c260cda, 0x689075b071c0b7b, 0xc290cdd0b8c0a26, 0x6010bf611c011b7, 0x68c07640b7e068d, 0xa560bfd11c30893, 0x11c60c350aec0b94, 0xc030b970a300c00, 0xc070b9a0a340a33, 0xc1b0b9e0a380a37, 0x7680b8206910c1f, 0xd000cfa0cf60690, 0xc0f11c90c380ce9, 0xbed11ba0c2c0ce0, 0xc2f0ce3076c0b86, 0x76f0b890bf011bd, 0x5d70999077b0bb4, 0x5e805ff0a2d0a2a, 0x6ae0b1306940a50, 0xba20722071f0b3a, 0xbc60bc20bbf0bbc, 0x8200c0b0bf90bf3, 0xd25082b08230cd5, 0x5d1092a09360869, 0x36c035d034a0337, 0x3a80399038a037b, 0x3490336032303b7, 0x389037a036b035c, 0x3fe03b603a70398, 0x42a041f04140409, 0x44a0443043c0435, 0xaf4047804710451, 0x0, 0x0, 0x0, 0x0, 0x26b4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe730e6b, 0x0, 0x256a258422132556, 0x26ae1ff91eff22c6, 0x202b25c8209226ae, 0x244a238221872090, 0x25a8251e250424e6, 0x233c22f0229a2254, 0x1f11265225bc24ca, 0x24e42302225e1fe3, 0x24dc22d620e6267a, 0x250a247821a12526, 0x232822a6221d211f, 0x1fb31f7d1f3125ae, 0x23922300225a21d5, 0x257e24ec24e02456, 0x23b02678266a2610, 0x25d624bc242c23d0, 0x212d206d2540267e, 0x23b4231a24682408, 0x20d4206b260c2566, 0x242422ca22b02256, 0x246e1fb125ec2438, 0x242e23e61f811f83, 0x21a3254e25f024c8, 0x20be1f0525462254, 0x1fc3237223322159, 0x1ef5214b1f3923b8, 0x1fed242221e12290, 0x253824cc23982063, 0x21ab228c25962276, 0x1f1f237021bb24a8, 0x241822441f7f1f5d, 0x1fb725c6253e2494, 0x21ef212720982011, 0x265625e423ba22d8, 0x220f1fa5268c2680, 0x217b210d2590226c, 0x22f622d021d12189, 0x2464243223e0234e, 0x25d8259c24d02588, 0x22ee201b1fa71f91, 0x2155211d25382514, 0x232c2406227221b7, 0x20f020be20491f27, 0x24502348233a215b, 0x2612260a25ca2460, 0x25c023da1f332630, 0x1f451f15216725fe, 0x225421e720d020c0, 0x25a624d6238022fc, 0x1fa325ea220726aa, 0x22be22a22233222d, 0x242023ae236e2340, 0x25f221911f612636, 0x258a22b220e21f3d, 0x23322237216b2143, 0x20d820091f9525f6, 0x2294224a222521fc, 0x251624462378233e, 0x1fcb260425c4251c, 0x235022fe200b22c0, 0x2682266e25f824de, 0x23f6247c22ae2231, 0x22ea2326240e23fc, 0x1f9724b01f23254c, 0x241421a521151f8f, 0x258e220d229c20b6, 0x2123252c25ee250e, 0x20371f4d, 0x220500002061, 0x238c232a1f850000, 0x23d823ce23cc23be, 0x245224102616, 0x2544000024e2, 0x25b4259e0000, 0x2642264000000000, 0x25fc25b026762644, 0x1faf1f511f471f35, 0x203d202f1fd51fb5, 0x20d62065205f2041, 0x21792175216120da, 0x220921f321db2185, 0x22ce22b622a82246, 0x23b22342230822f8, 0x23c623c223c42240, 0x23d623d423ca23c8, 0x2432240023f223e8, 0x24582444243a2436, 0x24ce249a249a2480, 0x254a2548252e2522, 0x259e259a256e256c, 0x215d263426282606, 0x248c274b, 0x1f2f1f5b1f7b1ef9, 0x1fbb1fad1f651f4f, 0x203b202d2025202f, 0x2094208e20692061, 0x2125212120aa20a2, 0x21712165214d213d, 0x2185217321792169, 0x21c921c521bf2193, 0x221f221d220521dd, 0x22a22276226e2229, 0x22dc22ce22c422c8, 0x2324230a23a422f8, 0x236a2358234a232a, 0x238e238c237e237c, 0x23b6239e23a02396, 0x2428240c240023f4, 0x24b2245824402432, 0x252a2524250024c6, 0x253c2544253a252e, 0x254a254225462548, 0x25a4258c256e2550, 0x260625f425ce25be, 0x262e262826202616, 0x272126ae265e2634, 0x1ea11e8d2733271f, 0x27a9277927671ea3, 0x26ac26a4, 0x0, 0xade0ae30adf0adb, 0xd280d280ae2, 0x0, 0x0, 0x134e000000000000, 0x134b135113481345, 0x0, 0x13d2000013850000, 0x1374136f135413a6, 0x13b7139b1360138e, 0x13ca13c702f413cd, 0x1359135613c313bf, 0x1371136c1364135c, 0x137f137c1376, 0x1390138b13881382, 0x139d00001398, 0x13a8000013a313a0, 0x13b413b1000013ab, 0x137913cf13bc13b9, 0x135f13ae13931367, 0x181e181e18181818, 0x18201820181e181e, 0x1824182418201820, 0x181c181c18241824, 0x18221822181c181c, 0x181a181a18221822, 0x183c183c181a181a, 0x183e183e183c183c, 0x18281828183e183e, 0x1826182618281828, 0x182a182a18261826, 0x182c182c182a182a, 0x18321832182c182c, 0x1834183418301830, 0x18381838182e182e, 0x1840184018361836, 0x1844184418401840, 0x1848184818441844, 0x1846184618481848, 0x184a184a18461846, 0x184c184c184c184c, 0x18501850186d186d, 0x184e184e18501850, 0x15911591184e184e, 0x186a186a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1842000000000000, 0x1803184218421842, 0x180717ff17ff1803, 0x18621862185b1807, 0x1860186018551855, 0x180b180b180b180b, 0x17cd17cd14151415, 0x17f117f1180d180d, 0x17fd17fd18011801, 0x1809180918051805, 0x17f517f517f51809, 0x1864186418641864, 0x17f517e517d517d1, 0x13fe13f713f417f9, 0x141e14171414140b, 0x146a144d1438142d, 0x1484147b1472146d, 0x14311422148c1487, 0x143c14d414d11435, 0x151a150c150514fa, 0x15a515a215931562, 0x15c815c515ba15b0, 0x1607157515e415df, 0x16451642163f160a, 0x165b16561653164c, 0x1679167416711662, 0x16851682167f167c, 0x16aa169616931688, 0x1579158c16c816b9, 0x14591455145116e0, 0x172d1461145d1526, 0x17691758174f1740, 0x177f17741771176c, 0x17aa17a3179c1782, 0x14e417c717c417b3, 0x64005d179714ee, 0x8000790072006b, 0x17e917e517e117dd, 0x140813db17f917f5, 0x14171414140e140b, 0x1464144d144a1447, 0x14781475146d146a, 0x14871484147e147b, 0x1674167116561653, 0x1693168816851679, 0x16e01579158c1696, 0x17551752152616e5, 0x176c176917631758, 0x17b317b017ad1797, 0x17d117c717c417be, 0x17ed17e517d917d5, 0x140b13fe13f713f4, 0x1438142d141e1411, 0x148c147b1467144d, 0x14d1143514311422, 0x150c150514fa143c, 0x1593156d1562151a, 0x15ba15b015a515a2, 0x157515e415df15c5, 0x1642163f160a1607, 0x1662165b164c1645, 0x16851682167f167c, 0x16c816b916aa1688, 0x1455145113e0158c, 0x1740172d15261459, 0x177117661758174f, 0x17a3179c17851774, 0x17e515ed17b317aa, 0x144d1411140b17ed, 0x151a1481147b1467, 0x16851557154c1529, 0x17661758158c1688, 0x162c162515ed17b3, 0x15ff15da15d71633, 0x152c161c16191602, 0x1490155d155a152f, 0x1440142a142613fb, 0x15bd159d159a1402, 0x1546153b153415c0, 0x157015171549154c, 0x15ff15da15d715b7, 0x152c161c16191602, 0x1490155d155a152f, 0x1440142a142613fb, 0x15bd159d159a1402, 0x1546153b153415c0, 0x157015171549154c, 0x1546153b153415b7, 0x15c815571529154c, 0x1534150c150514fa, 0x15df15c81546153b, 0x13e313e3, 0x0, 0x0, 0x0, 0x0, 0x1434143014301421, 0x145814541450143b, 0x14c114c514a314a3, 0x1521150114fd1508, 0x15251525151d1521, 0x153e159615651565, 0x154f154f1537153e, 0x15b315a815531553, 0x15cf15cb15cb15b3, 0x15f315f315e715d3, 0x16111615160d15f7, 0x1669166516481648, 0x16ad16c016c416bc, 0x16d216cb16cb16ad, 0x170b170216fe16d2, 0x1716171216f316eb, 0x177716ef00000000, 0x173417471743177b, 0x175b175f17381734, 0x1429140117b617b6, 0x1460143f14431425, 0x14a7148f14ab145c, 0x15ac15421569150f, 0x179f17a616d616b5, 0x174b166d172117ba, 0x168f15fb16bc1665, 0x168b16b1171a1730, 0x14ba1493173016b1, 0x168b13fa164f16f7, 0x173c1513159615e7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13d913de165e158f, 0x15eb14e915731706, 0x1497157c1578158a, 0x14f1, 0x0, 0x0, 0x0, 0x0, 0x5401b331b3102f6, 0x1b770093008d0546, 0x2ff1b79, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9931a6b1a6d02fc, 0xe3b00a500a10993, 0x1b451b4f1b4b0e3f, 0x1b351b3b1b391b47, 0x1b411b3f1b3d1b37, 0x98b000000001b43, 0xc000c000c098f, 0x99309930993000c, 0x2fa1b3102f6, 0x8d009305400546, 0xe3b00a500a11a6d, 0x971b4f1b4b0e3f, 0x2f802f402f2009d, 0x54405590548, 0x566009b0099098d, 0x0, 0x5a161f0057, 0x1622006800000061, 0x163000761629006f, 0x163a00841637007d, 0x13e913e613e613d5, 0x13ec178f178f13e9, 0x17ca17ca17ca13ec, 0x13f213d713d717ca, 0x141a13f213f213f2, 0x141c141c141c141a, 0x147014701470141c, 0x13f513f513f51470, 0x13f813f813f813f5, 0x13ff13ff13ff13f8, 0x14e214e014e013ff, 0x140913dc13dc14e2, 0x14f814f814f81409, 0x15321532153214f8, 0x1560156015601532, 0x15a015a015a01560, 0x15c315c315c315a0, 0x15dd15dd15dd15c3, 0x15e215e215e215dd, 0x16051605160515e2, 0x163d163d163d1605, 0x165916591659163d, 0x1677167716771659, 0x14ec14ec14ec1677, 0x140c140c140c14ec, 0x140f140f140f140c, 0x13e113e113e1140f, 0x14151788178813e1, 0x13fc13fc13fc1415, 0x16a2169e169e13fc, 0x169b16a616a616a2, 0x169b, 0x970095008d0000, 0x9f009d009b0099, 0x2f402f200a500a1, 0x30302fa02f802f6, 0x30f034303140305, 0x392038303740365, 0x546054003b003a1, 0x93055905440548, 0x5e305d505680566, 0x687067e062905e6, 0x71a060706cf06ac, 0x7a4077e07230734, 0x85e082c083b06af, 0x77006b2056b088d, 0x98b060a095a0682, 0x9930991098f098d, 0x9a0093706920995, 0x6020ad90a7d0a2e, 0xb79073e0ae00b0d, 0x7870b3b05d30a28, 0x8400a1105d80cd3, 0xde1086a0a240ba3, 0xe3b061106950b41, 0x1b280e410e3f0e3d, 0x1b3f1b3d1b331b2a, 0x1bd61e551e5c1b31, 0x1c181c081bfd1bef, 0x1cee1e171e0f1e02, 0x1bff1bf11bd81c16, 0x1c411c241c1a1c0a, 0x1cad1c991c901c73, 0x1cda1ccd1c1e1cbd, 0x1cf51cf01bfb1cdf, 0x1d111d0f1ca61bde, 0x1d391d1b1d0d1c8e, 0x1c311d9f1d741d55, 0x1e001def1c221ddc, 0x1e1b1e191e111e04, 0x1c5d1e341bed1c35, 0x8b00881c061e42, 0x1a101949194419d4, 0x19501a141a12194b, 0x1a181a1619571955, 0x1a201a1e1a1c1a1a, 0x19661961195c19a6, 0x196f196d196819b0, 0x198e198319811977, 0x199d19981993, 0x19d8194700000000, 0x19e019de19dc19da, 0x19e419e200000000, 0x19ec19ea19e8198c, 0x197519ee00000000, 0x19f819f619f419f2, 0x197f19fa00000000, 0x19fe, 0x90e4b0e450e43, 0x1a820e470e49, 0x1a8b1a891a841b22, 0x1b261b241a90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26b600000000, 0x26b9, 0x0, 0x0, 0x26bc000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26c226bf00000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26c826c500000000, 0x26d726d326cf26cb, 0x26db, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26df000000000000, 0x26e626ed26e226ea, 0x26f1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae000000602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e300000568, 0x68700000000, 0x71a06070000, 0x6af07a4077e0000, 0x88d085e0000083b, 0x682077006b2056b, 0x9370692060a095a, 0xad900000a2e09a0, 0x73e0ae00b0d0000, 0xb3b05d30a280b79, 0xa1105d80cd30000, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e6000005d50568, 0x687067e0629, 0x734071a06070000, 0x6af07a4077e0723, 0x88d085e0000083b, 0x682077006b2056b, 0x93706920000095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e6000005d50568, 0x687067e0629, 0x734071a060706cf, 0x7a400000723, 0x88d085e00000000, 0x682077006b2056b, 0x93706920000095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x61106950b410de1, 0xe800e6f, 0xf3c0f3a0f380ee3, 0xfad0f5e0f5c0f3e, 0xfe20fe00fde0faf, 0x10060fe80fe60fe4, 0x100f100d0fad1008, 0x1035103310311011, 0x10ea10861aa3077c, 0x110e10f010ee10ec, 0x11ae1170116e1110, 0x11ce11cc11b211b0, 0x11f811f011ee11d0, 0x123c11fe11fc11fa, 0x1a9e12421240123e, 0x123c11ae116e10f0, 0xf380ee311ee11f0, 0xf5c0f3e0f3c0f3a, 0xfde0faf0fad0f5e, 0xfe60fe40fe20fe0, 0xfad100810060fe8, 0x10311011100f100d, 0x1aa3077c10351033, 0x10ee10ec10ea1086, 0x116e1110110e10f0, 0x11b211b011ae1170, 0x11ee11d011ce11cc, 0x11fc11fa11f811f0, 0x1240123e123c11fe, 0x116e10f01a9e1242, 0x11ee11f0123c11ae, 0xf3c0f3a0f380ee3, 0xfad0f5e0f5c0f3e, 0xfe20fe00fde0faf, 0x10060fe80fe60fe4, 0x100f100d0fad1008, 0x1035103310311011, 0x10ea10861aa3077c, 0x110e10f010ee10ec, 0x11ae1170116e1110, 0x11ce11cc11b211b0, 0x11f811f011ee11d0, 0x123c11fe11fc11fa, 0x1a9e12421240123e, 0x123c11ae116e10f0, 0xf380ee311ee11f0, 0xf5c0f3e0f3c0f3a, 0xfde0faf0fad0f5e, 0xfe60fe40fe20fe0, 0xfad100810060fe8, 0x10311011100f100d, 0x1aa3077c10351033, 0x10ee10ec10ea1086, 0x116e1110110e10f0, 0x11b211b011ae1170, 0x11ee11d011ce11cc, 0x11fc11fa11f811f0, 0x1240123e123c11fe, 0x116e10f01a9e1242, 0x11ee11f0123c11ae, 0xf3c0f3a0f380ee3, 0xfad0f5e0f5c0f3e, 0xfe20fe00fde0faf, 0x10060fe80fe60fe4, 0x100f100d0fad1008, 0x1035103310311011, 0x10ea10861aa3077c, 0x110e10f010ee10ec, 0x11ae1170116e1110, 0x11ce11cc11b211b0, 0x11f811f011ee11d0, 0x123c11fe11fc11fa, 0x1a9e12421240123e, 0x123c11ae116e10f0, 0x12a212a011ee11f0, 0x314030500000000, 0x3740365030f0343, 0x3b003a103920383, 0x30f034303140305, 0x392038303740365, 0x314030503b003a1, 0x3740365030f0343, 0x3b003a103920383, 0x30f034303140305, 0x392038303740365, 0x314030503b003a1, 0x3740365030f0343, 0x3b003a103920383, 0x14e013f513f213d7, 0x13f8140917880000, 0x14ec167713fc15c3, 0x15e214f8140f140c, 0x13dc16591560163d, 0x13ff1470141c1532, 0x160515dd15a014e2, 0x1816183a184a1814, 0x13f513f20000, 0x13f80000000013e1, 0x14ec167713fc0000, 0x15e214f8140f140c, 0x16591560163d, 0x13ff1470141c1532, 0x1605000015a00000, 0x0, 0x13f500000000, 0x13f8000000000000, 0x14ec000013fc0000, 0x15e214f8140f0000, 0x165915600000, 0x13ff000000001532, 0x1605000015a00000, 0x18160000184a0000, 0x13f513f20000, 0x13f80000000013e1, 0x167713fc15c3, 0x15e214f8140f140c, 0x16591560163d, 0x13ff1470141c1532, 0x160515dd15a00000, 0x183a00001814, 0x14e013f513f213d7, 0x13f81409178813e1, 0x14ec000013fc15c3, 0x15e214f8140f140c, 0x13dc16591560163d, 0x13ff1470141c1532, 0x160515dd15a014e2, 0x0, 0x14e013f513f20000, 0x13f8140917880000, 0x14ec000013fc15c3, 0x15e214f8140f140c, 0x13dc16591560163d, 0x13ff1470141c1532, 0x160515dd15a014e2, 0x0, 0x3f103160307030a, 0x4fa04de04ab0468, 0x5310520050b, 0x0, 0x10a0106010200fe, 0x11a01160112010e, 0x12a01260122011e, 0x13a01360132012e, 0x14a01460142013e, 0x15a01560152014e, 0x5e31b4d0162015e, 0x93305e5082c, 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, 0x76c06b1060a095a, 0x930082708660860, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x761075e00000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x606, 0x0, 0x0, 0x0, 0x1cad1c9e1bc3, 0x0, 0x0, 0x0, 0x1cf71ff320b02197, 0x208c253220811f17, 0x21e722f221fe1f1d, 0x21eb1f6921451f9d, 0x2560235c24261f93, 0x219d22cc200f2073, 0x25a01eef1ee921b3, 0x21ad20011efd20fa, 0x23f023d221992574, 0x329221b22bc2005, 0x20351f9f2366, 0x0, 0x1b5d1b551b511b69, 0x1b591b711b611b6d, 0x1b65, 0x0, 0x1ffd2147, 0x0, 0x0, 0x0, 0x26f51f0b1f031f07, 0x1f3b1f371f351f2d, 0x1f431f471f411f3f, 0x1f531f5126fd1e63, 0x1e6526f71f631f55, 0x1f7126fb1f691f59, 0x1f7b1f791f251f75, 0x1e691f8d1f8927b9, 0x1fa11f9f1f9b1f99, 0x1fb51faf1fad1e6b, 0x1fc31fbf1fbd1fbb, 0x1fe11fd91fd51fd3, 0x1fe71fe71fe71fe5, 0x1ff51ff122e42703, 0x20031fff1ffb2705, 0x20152013200d2017, 0x2023201f201d2019, 0x202d202920292027, 0x204b203920332031, 0x2043203f204d203d, 0x2057205520711f8f, 0x205b205d20532059, 0x2077207527072067, 0x209620852081207b, 0x209e209c270b2709, 0x1e6d20a4209a20a0, 0x20ac20ac20a81e6f, 0x20be20bc20ba270d, 0x20c820c6270f20c2, 0x20d21e7120cc2137, 0x271320de20e020da, 0x20e820ea271520e4, 0x1e7320f620f420ec, 0x21062104210220fe, 0x21171e7727171e75, 0x27cd211f211b2119, 0x2486271b271b212b, 0x27291e7921332133, 0x1e7b213f213b277d, 0x2157215321512149, 0x21611e7d1e7f215f, 0x216f216d2163271d, 0x21792177216f2171, 0x2183217f217d2181, 0x218f210b21872185, 0x21b121a7219f219b, 0x21b521a921af2723, 0x21c7272521c321b9, 0x21cb1e8121bd21c1, 0x1e8321cd21d321cf, 0x21f5272721df21db, 0x22091e8922032215, 0x1f6d1f6b1e851e87, 0x1ebb2470220b2217, 0x222b2221221f221d, 0x22351e8b27312227, 0x273522462242222f, 0x1e8d224c22392248, 0x225822522250224e, 0x22621e8f225c2737, 0x226a1e9122642739, 0x273b227822762270, 0x273f2288273d2711, 0x2298228a2292228e, 0x22a422a222a822a0, 0x229e274122ac22aa, 0x22c41e9322ba22b8, 0x22d222b4274322c2, 0x22de22d427472745, 0x22e01e9522da22dc, 0x26f922ec22e622e8, 0x274d22fa274922f4, 0x274f2314230a2304, 0x275327512320231e, 0x23381e972336232e, 0x234623441e991e99, 0x1e9b2352234c234a, 0x2757236c2755235e, 0x2759237a27192372, 0x1e9f1e9d275d275b, 0x2763275f27612396, 0x239c239c239a2765, 0x1ea523a21ea323a0, 0x23b023ac27691ea7, 0x23c8276b1ea923b6, 0x23e423d8276f276d, 0x23ec23ea23e81eab, 0x23f8277327732771, 0x2404240227751ead, 0x1eb1241227771eaf, 0x277b241e2416241a, 0x243424301eb3242a, 0x2781277f1eb5243c, 0x2785244827831eb7, 0x278724582454244e, 0x2466278b24622789, 0x247424721eb9272b, 0x278d20a624761ebd, 0x2486272f272d278f, 0x249e1ebf25942488, 0x24a21fa924a0249c, 0x279124aa24a624a4, 0x24b824b624ac24a8, 0x24ce24c424ba24ae, 0x24c224c024be24b4, 0x1ec1279527972793, 0x279f24d824d424d2, 0x1ec51ec3279924da, 0x24ea1ec7279d279b, 0x24f624f024ee24ec, 0x250024f824fa24f4, 0x1ec9250224fe24fc, 0x25101ecb25082506, 0x251a251827a12512, 0x27a31e6725201ecd, 0x25361ed11ecf27a5, 0x27a7255825502542, 0x2576257025642562, 0x257a257c26ff27ab, 0x258c258627012580, 0x25b225ac27af27ad, 0x25cc25b827b125b6, 0x25da25d025d425d2, 0x1ed325e227b325dc, 0x26021ed527b525e6, 0x27bb27b7260e20ee, 0x27bd26221ed91ed7, 0x262e262e27bf1edb, 0x1edd263e27c12632, 0x26542650264c2646, 0x266c265e27c31edf, 0x26741ee31ee12672, 0x27c927c71ee527c5, 0x26901ee7268627cb, 0x269e269a26962694, 0x27cf26a2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); diff --git a/std/internal/unicode_tables.d b/std/internal/unicode_tables.d index 55b31a136b0..38cc7239bf3 100644 --- a/std/internal/unicode_tables.d +++ b/std/internal/unicode_tables.d @@ -8,13 +8,16 @@ //Automatically generated from Unicode Character Database files module std.internal.unicode_tables; -@safe pure nothrow: +@safe pure nothrow @nogc: struct SimpleCaseEntry { uint ch; ubyte n, bucket;// n - number in bucket + +pure nothrow @nogc: + @property ubyte size() const { return bucket & 0x3F; @@ -35,7 +38,7 @@ struct FullCaseEntry ubyte n, size;// n number in batch, size - size of batch ubyte entry_len; - @property auto value() const @trusted + @property auto value() const @trusted pure nothrow @nogc return { return seq[0..entry_len]; } @@ -1851,7 +1854,7 @@ _U("Yi Syllables", Yi_Syllables), struct scripts { private alias _U = immutable(UnicodeProperty); -@property static _U[] tab() pure { return _tab; } +@property static _U[] tab() pure nothrow @nogc { return _tab; } static immutable: private alias _T = ubyte[]; _T Buhid = [0x97, 0x40, 0x14]; @@ -2065,7 +2068,7 @@ _U("Yi", Yi), struct hangul { private alias _U = immutable(UnicodeProperty); -@property static _U[] tab() pure { return _tab; } +@property static _U[] tab() pure nothrow @nogc { return _tab; } static immutable: private alias _T = ubyte[]; _T LVT = [0xa0, 0xac, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b]; diff --git a/std/internal/windows/advapi32.d b/std/internal/windows/advapi32.d index 5c5344cb6b0..86147706e56 100644 --- a/std/internal/windows/advapi32.d +++ b/std/internal/windows/advapi32.d @@ -4,7 +4,7 @@ * The only purpose of this module is to do the static construction for * std.windows.registry, to eliminate cyclic construction errors. * - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Kenji Hara * Source: $(PHOBOSSRC std/internal/windows/_advapi32.d) */ diff --git a/std/json.d b/std/json.d index d93dc982825..68549747a1c 100644 --- a/std/json.d +++ b/std/json.d @@ -3,8 +3,44 @@ /** JavaScript Object Notation +Synopsis: +---- + //parse a file or string of json into a usable structure + string s = "{ \"language\": \"D\", \"rating\": 3.14, \"code\": \"42\" }"; + JSONValue j = parseJSON(s); + writeln("Language: ", j["language"].str(), + " Rating: ", j["rating"].floating() + ); + + // j and j["language"] return JSONValue, + // j["language"].str returns a string + + //check a type + long x; + if (j["code"].type() == JSON_TYPE.INTEGER) + { + x = j["code"].integer; + } + else + { + x = to!int(j["code"].str); + } + + // create a json struct + JSONValue jj = [ "language": "D" ]; + // rating doesnt exist yet, so use .object to assign + jj.object["rating"] = JSONValue(3.14); + // create an array to assign to list + jj.object["list"] = JSONValue( ["a", "b", "c"] ); + // list already exists, so .object optional + jj["list"].array ~= JSONValue("D"); + + s = j.toString(); + writeln(s); +---- + Copyright: Copyright Jeremie Pelletier 2008 - 2009. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: Jeremie Pelletier, David Herberth References: $(LINK http://json.org/) Source: $(PHOBOSSRC std/_json.d) @@ -17,19 +53,10 @@ Distributed under the Boost Software License, Version 1.0. */ module std.json; -import std.ascii; import std.conv; -import std.range; -import std.utf; +import std.range.primitives; +import std.array; import std.traits; -import std.exception; - -private -{ - // Prevent conflicts from these generic names - alias UTFStride = std.utf.stride; - alias toUnicode = std.utf.decode; -} /** JSON type enumeration @@ -37,15 +64,15 @@ JSON type enumeration enum JSON_TYPE : byte { /// Indicates the type of a $(D JSONValue). - STRING, + NULL, + STRING, /// ditto INTEGER, /// ditto UINTEGER,/// ditto FLOAT, /// ditto OBJECT, /// ditto ARRAY, /// ditto TRUE, /// ditto - FALSE, /// ditto - NULL /// ditto + FALSE /// ditto } /** @@ -53,6 +80,8 @@ JSON value node */ struct JSONValue { + import std.exception : enforceEx, enforce; + union Store { string str; @@ -65,11 +94,21 @@ struct JSONValue private Store store; private JSON_TYPE type_tag; - /// Specifies the _type of the value stored in this structure. + /** + Returns the JSON_TYPE of the value stored in this structure. + */ @property JSON_TYPE type() const { return type_tag; } + /// + unittest + { + string s = "{ \"language\": \"D\" }"; + JSONValue j = parseJSON(s); + assert(j.type() == JSON_TYPE.OBJECT); + assert(j["language"].type() == JSON_TYPE.STRING); + } /** $(RED Deprecated. Instead, please assign the value with the adequate @@ -128,6 +167,20 @@ struct JSONValue assign(v); return store.str; } + /// + unittest + { + JSONValue j = [ "language": "D" ]; + + // get value + assert(j["language"].str() == "D"); + // str() or str is ok + assert(j["language"].str == "D"); + + // change existing key to new string + j["language"].str("Perl"); + assert(j["language"].str == "Perl"); + } /// Value getter/setter for $(D JSON_TYPE.INTEGER). /// Throws $(D JSONException) for read access if $(D type) is not $(D JSON_TYPE.INTEGER). @@ -204,6 +257,12 @@ struct JSONValue return store.array; } + /// Test whether the type is $(D JSON_TYPE.NULL) + @property bool isNull() const + { + return type == JSON_TYPE.NULL; + } + private void assign(T)(T arg) { static if(is(T : typeof(null))) @@ -318,6 +377,18 @@ struct JSONValue store = arg.store; type_tag = arg.type; } + /// + unittest + { + JSONValue j = JSONValue( "a string" ); + j = JSONValue(42); + + j = JSONValue( [1, 2, 3] ); + assert(j.type() == JSON_TYPE.ARRAY); + + j = JSONValue( ["language": "D"] ); + assert(j.type() == JSON_TYPE.OBJECT); + } void opAssign(T)(T arg) if(!isStaticArray!T && !is(T : JSONValue)) { @@ -339,6 +410,13 @@ struct JSONValue "JSONValue array index is out of range"); return store.array[i]; } + /// + unittest + { + JSONValue j = JSONValue( [42, 43, 44] ); + assert( j[0].integer == 42 ); + assert( j[1].integer == 43 ); + } /// Hash syntax for json objects. /// Throws $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT). @@ -349,6 +427,35 @@ struct JSONValue return *enforce!JSONException(k in store.object, "Key not found: " ~ k); } + /// + unittest + { + JSONValue j = JSONValue( ["language": "D"] ); + assert( j["language"].str() == "D" ); + } + + /// Operator sets $(D value) for element of JSON object by $(D key) + /// If JSON value is null, then operator initializes it with object and then + /// sets $(D value) for it. + /// Throws $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT) + /// or $(D JSON_TYPE.NULL). + void opIndexAssign(T)(auto ref T value, string key) + { + enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL, + "JSONValue must be object or null"); + + if(type == JSON_TYPE.NULL) + this = (JSONValue[string]).init; + + store.object[key] = value; + } + /// + unittest + { + JSONValue j = JSONValue( ["language": "D"] ); + j["language"].str = "Perl"; + assert( j["language"].str == "Perl" ); + } void opIndexAssign(T)(T arg, size_t i) { @@ -358,12 +465,12 @@ struct JSONValue "JSONValue array index is out of range"); store.array[i] = arg; } - - void opIndexAssign(T)(T arg, string k) + /// + unittest { - enforceEx!JSONException(type == JSON_TYPE.OBJECT, - "JSONValue is not an object"); - store.object[k] = arg; + JSONValue j = JSONValue( ["Perl", "C"] ); + j[1].str = "D"; + assert( j[1].str == "D" ); } JSONValue opBinary(string op : "~", T)(T arg) @@ -416,6 +523,12 @@ struct JSONValue "JSONValue is not an object"); return k in store.object; } + /// + unittest + { + JSONValue j = [ "language": "D", "author": "walter" ]; + string a = ("author" in j).str; + } /// Implements the foreach $(D opApply) interface for json arrays. int opApply(int delegate(size_t index, ref JSONValue) dg) @@ -470,6 +583,9 @@ Parses a serialized string and returns a tree of JSON values. */ JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T) { + import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower; + import std.utf : toUTF8; + JSONValue root = void; root.type_tag = JSON_TYPE.NULL; @@ -477,20 +593,38 @@ JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T) int depth = -1; dchar next = 0; - int line = 1, pos = 1; + int line = 1, pos = 0; void error(string msg) { throw new JSONException(msg, line, pos); } + dchar popChar() + { + if (json.empty) error("Unexpected end of data."); + dchar c = json.front; + json.popFront(); + + if(c == '\n') + { + line++; + pos = 0; + } + else + { + pos++; + } + + return c; + } + dchar peekChar() { if(!next) { if(json.empty) return '\0'; - next = json.front; - json.popFront(); + next = popChar(); } return next; } @@ -511,21 +645,7 @@ JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T) next = 0; } else - { - if(json.empty) error("Unexpected end of data."); - c = json.front; - json.popFront(); - } - - if(c == '\n' || (c == '\r' && peekChar() != '\n')) - { - line++; - pos = 1; - } - else - { - pos++; - } + c = popChar(); return c; } @@ -598,7 +718,7 @@ JSONValue parseJSON(T)(T json, int maxDepth = -1) if(isInputRange!T) goto Next; } - return str.data ? str.data : ""; + return str.data.length ? str.data : ""; } void parseValue(JSONValue* value) @@ -938,6 +1058,7 @@ class JSONException : Exception unittest { + import std.exception; JSONValue jv = "123"; assert(jv.type == JSON_TYPE.STRING); assertNotThrown(jv.str); @@ -993,6 +1114,12 @@ unittest assert(index == (value.integer-3)); } + jv = null; + assert(jv.type == JSON_TYPE.NULL); + assert(jv.isNull); + jv = "foo"; + assert(!jv.isNull); + jv = JSONValue("value"); assert(jv.type == JSON_TYPE.STRING); assert(jv.str == "value"); @@ -1081,6 +1208,8 @@ unittest unittest { + import std.exception; + // An overly simple test suite, if it can parse a serializated string and // then use the resulting values tree to generate an identical // serialization, both the decoder and encoder works. @@ -1170,6 +1299,7 @@ unittest { deprecated unittest { // Bugzilla 12332 + import std.exception; JSONValue jv; jv.type = JSON_TYPE.INTEGER; @@ -1190,3 +1320,50 @@ deprecated unittest jv.type = JSON_TYPE.TRUE; assert(jv.type == JSON_TYPE.TRUE); } + +unittest +{ + // Bugzilla 12969 + + JSONValue jv; + jv["int"] = 123; + + assert(jv.type == JSON_TYPE.OBJECT); + assert("int" in jv); + assert(jv["int"].integer == 123); + + jv["array"] = [1, 2, 3, 4, 5]; + + assert(jv["array"].type == JSON_TYPE.ARRAY); + assert(jv["array"][2].integer == 3); + + jv["str"] = "D language"; + assert(jv["str"].type == JSON_TYPE.STRING); + assert(jv["str"].str == "D language"); + + jv["bool"] = false; + assert(jv["bool"].type == JSON_TYPE.FALSE); + + assert(jv.object.length == 4); + + jv = [5, 4, 3, 2, 1]; + assert( jv.type == JSON_TYPE.ARRAY ); + assert( jv[3].integer == 2 ); +} + +unittest +{ + auto s = q"EOF +[ + 1, + 2, + 3, + potato +] +EOF"; + + import std.exception; + + auto e = collectException!JSONException(parseJSON(s)); + assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg); +} diff --git a/std/math.d b/std/math.d index 1586c78eba4..25016fa9014 100644 --- a/std/math.d +++ b/std/math.d @@ -1,12 +1,61 @@ // Written in the D programming language. /** - * Elementary mathematical functions - * * Contains the elementary mathematical functions (powers, roots, * and trigonometric functions), and low-level floating-point operations. - * Mathematical special functions are available in std.mathspecial. + * Mathematical special functions are available in $(D std.mathspecial). * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Members) ) +$(TR $(TDNW Constants) $(TD + $(MYREF E) $(MYREF PI) $(MYREF PI_2) $(MYREF PI_4) $(MYREF M_1_PI) + $(MYREF M_2_PI) $(MYREF M_2_SQRTPI) $(MYREF LN10) $(MYREF LN2) + $(MYREF LOG2) $(MYREF LOG2E) $(MYREF LOG2T) $(MYREF LOG10E) + $(MYREF SQRT2) $(MYREF SQRT1_2) +)) +$(TR $(TDNW Classics) $(TD + $(MYREF abs) $(MYREF fabs) $(MYREF sqrt) $(MYREF cbrt) $(MYREF hypot) $(MYREF poly) +)) +$(TR $(TDNW Trigonometry) $(TD + $(MYREF sin) $(MYREF cos) $(MYREF tan) $(MYREF asin) $(MYREF acos) + $(MYREF atan) $(MYREF atan2) $(MYREF sinh) $(MYREF cosh) $(MYREF tanh) + $(MYREF asinh) $(MYREF acosh) $(MYREF atanh) $(MYREF expi) +)) +$(TR $(TDNW Rounding) $(TD + $(MYREF ceil) $(MYREF floor) $(MYREF round) $(MYREF lround) + $(MYREF trunc) $(MYREF rint) $(MYREF lrint) $(MYREF nearbyint) + $(MYREF rndtol) +)) +$(TR $(TDNW Exponentiation & Logarithms) $(TD + $(MYREF pow) $(MYREF exp) $(MYREF exp2) $(MYREF expm1) $(MYREF ldexp) + $(MYREF frexp) $(MYREF log) $(MYREF log2) $(MYREF log10) $(MYREF logb) + $(MYREF ilogb) $(MYREF log1p) $(MYREF scalbn) +)) +$(TR $(TDNW Modulus) $(TD + $(MYREF fmod) $(MYREF modf) $(MYREF remainder) +)) +$(TR $(TDNW Floating-point operations) $(TD + $(MYREF approxEqual) $(MYREF feqrel) $(MYREF fdim) $(MYREF fmax) + $(MYREF fmin) $(MYREF fma) $(MYREF nextDown) $(MYREF nextUp) + $(MYREF nextafter) $(MYREF NaN) $(MYREF getNaNPayload) +)) +$(TR $(TDNW Introspection) $(TD + $(MYREF isFinite) $(MYREF isIdentical) $(MYREF isInfinity) $(MYREF isNaN) + $(MYREF isNormal) $(MYREF isSubnormal) $(MYREF signbit) $(MYREF sgn) + $(MYREF copysign) +)) +$(TR $(TDNW Complex Numbers) $(TD + $(MYREF abs) $(MYREF conj) $(MYREF sin) $(MYREF cos) $(MYREF expi) +)) +$(TR $(TDNW Hardware Control) $(TD + $(MYREF IeeeFlags) $(MYREF FloatingPointControl) +)) +) +) + * The functionality closely follows the IEEE754-2008 standard for * floating-point arithmetic, including the use of camelCase names rather * than C99-style lower case names. All of these functions behave correctly @@ -35,6 +84,8 @@ * $0 * SVH = $(TR $(TH $1) $(TH $2)) * SV = $(TR $(TD $1) $(TD $2)) + * TH3 = $(TR $(TH $1) $(TH $2) $(TH $3)) + * TD3 = $(TR $(TD $1) $(TD $2) $(TD $3)) * * NAN = $(RED NAN) * SUP = $0 @@ -63,7 +114,7 @@ * reserves the right to distribute this material elsewhere under different * copying permissions. These modifications are distributed here under * the following terms: - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright), Don Clugston, * Conversion of CEPHES math library to D by Iain Buclaw * Source: $(PHOBOSSRC std/_math.d) @@ -89,15 +140,10 @@ version(DigitalMars) version = INLINE_YL2X; // x87 has opcodes for these } -version (X86) -{ - version = X86_Any; -} - -version (X86_64) -{ - version = X86_Any; -} +version (X86) version = X86_Any; +version (X86_64) version = X86_Any; +version (PPC) version = PPC_Any; +version (PPC64) version = PPC_Any; version(D_InlineAsm_X86) { @@ -130,14 +176,14 @@ version(unittest) if (signbit(x) != signbit(y)) return 0; - if (isinf(x) && isinf(y)) + if (isInfinity(x) && isInfinity(y)) return 1; - if (isinf(x) || isinf(y)) + if (isInfinity(x) || isInfinity(y)) return 0; - if (isnan(x) && isnan(y)) + if (isNaN(x) && isNaN(y)) return 1; - if (isnan(x) || isnan(y)) + if (isNaN(x) || isNaN(y)) return 0; char[30] bufx; @@ -450,6 +496,7 @@ Num abs(Num)(Num x) @safe pure nothrow return x>=0 ? x : -x; } +/// ditto auto abs(Num)(Num z) @safe pure nothrow @nogc if (is(Num* : const(cfloat*)) || is(Num* : const(cdouble*)) || is(Num* : const(creal*))) @@ -457,7 +504,7 @@ auto abs(Num)(Num z) @safe pure nothrow @nogc return hypot(z.re, z.im); } -/** ditto */ +/// ditto real abs(Num)(Num y) @safe pure nothrow @nogc if (is(Num* : const(ifloat*)) || is(Num* : const(idouble*)) || is(Num* : const(ireal*))) @@ -465,8 +512,8 @@ real abs(Num)(Num y) @safe pure nothrow @nogc return fabs(y.im); } - -unittest +/// ditto +@safe pure nothrow @nogc unittest { assert(isIdentical(abs(-0.0L), 0.0L)); assert(isNaN(abs(real.nan))); @@ -497,8 +544,8 @@ ireal conj(ireal y) @safe pure nothrow @nogc return -y; } - -unittest +/// +@safe pure nothrow @nogc unittest { assert(conj(7 + 3i) == 7-3i); ireal z = -3.2Li; @@ -520,23 +567,43 @@ unittest real cos(real x) @safe pure nothrow @nogc; /* intrinsic */ /*********************************** - * Returns sine of x. x is in radians. + * Returns $(WEB en.wikipedia.org/wiki/Sine, sine) of x. x is in $(WEB en.wikipedia.org/wiki/Radian, radians). * * $(TABLE_SV - * $(TR $(TH x) $(TH sin(x)) $(TH invalid?)) - * $(TR $(TD $(NAN)) $(TD $(NAN)) $(TD yes)) - * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no)) - * $(TR $(TD $(PLUSMNINF)) $(TD $(NAN)) $(TD yes)) + * $(TH3 x , sin(x) , invalid?) + * $(TD3 $(NAN) , $(NAN) , yes ) + * $(TD3 $(PLUSMN)0.0, $(PLUSMN)0.0, no ) + * $(TD3 $(PLUSMNINF), $(NAN) , yes ) * ) + * + * Params: + * x = angle in radians (not degrees) + * Returns: + * sine of x + * See_Also: + * $(MYREF cos), $(MYREF tan), $(MYREF asin) * Bugs: * Results are undefined if |x| >= $(POWER 2,64). */ real sin(real x) @safe pure nothrow @nogc; /* intrinsic */ +/// +unittest +{ + import std.math : sin, PI; + import std.stdio : writefln; + + void someFunc() + { + real x = 30.0; + auto result = sin(x * (PI / 180)); // convert degrees to radians + writefln("The sine of %s degrees is %s", x, result); + } +} /*********************************** - * sine, complex and imaginary + * Returns sine for complex and imaginary arguments. * * sin(z) = sin(z.re)*cosh(z.im) + cos(z.re)*sinh(z.im)i * @@ -556,7 +623,8 @@ ireal sin(ireal y) @safe pure nothrow @nogc return cosh(y.im)*1i; } -unittest +/// +@safe pure nothrow @nogc unittest { assert(sin(0.0+0.0i) == 0.0); assert(sin(2.0+0.0i) == sin(2.0L) ); @@ -580,7 +648,8 @@ real cos(ireal y) @safe pure nothrow @nogc return cosh(y.im); } -unittest +/// +@safe pure nothrow @nogc unittest { assert(cos(0.0+0.0i)==1.0); assert(cos(1.3L+0.0i)==cos(1.3L)); @@ -602,7 +671,7 @@ real tan(real x) @trusted pure nothrow @nogc { version(D_InlineAsm_X86) { - asm + asm pure nothrow @nogc { fld x[EBP] ; // load theta fxam ; // test for oddball values @@ -638,19 +707,19 @@ Lret: {} { version (Win64) { - asm + asm pure nothrow @nogc { fld real ptr [RCX] ; // load theta } } else { - asm + asm pure nothrow @nogc { fld x[RBP] ; // load theta } } - asm + asm pure nothrow @nogc { fxam ; // test for oddball values fstsw AX ; @@ -749,7 +818,7 @@ Lret: {} } } -unittest +@safe nothrow @nogc unittest { static real[2][] vals = // angle,tan [ @@ -977,7 +1046,7 @@ real atan2(real y, real x) @trusted pure nothrow @nogc { version (Win64) { - asm { + asm pure nothrow @nogc { naked; fld real ptr [RDX]; // y fld real ptr [RCX]; // x @@ -987,7 +1056,7 @@ real atan2(real y, real x) @trusted pure nothrow @nogc } else { - asm { + asm pure nothrow @nogc { fld y; fld x; fpatan; @@ -1177,7 +1246,7 @@ creal coshisinh(real x) @safe pure nothrow @nogc } } -unittest +@safe pure nothrow @nogc unittest { creal c = coshisinh(3.0L); assert(c.re == cosh(3.0L)); @@ -1343,7 +1412,7 @@ double sqrt(double x) @nogc @safe pure nothrow; /* intrinsic */ /// ditto real sqrt(real x) @nogc @safe pure nothrow; /* intrinsic */ -unittest +@safe pure nothrow @nogc unittest { //ctfe enum ZX80 = sqrt(7.0f); @@ -1393,10 +1462,10 @@ creal sqrt(creal z) @nogc @safe pure nothrow } /** - * Calculates e$(SUP x). + * Calculates e$(SUPERSCRIPT x). * * $(TABLE_SV - * $(TR $(TH x) $(TH e$(SUP x)) ) + * $(TR $(TH x) $(TH e$(SUPERSCRIPT x)) ) * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) ) * $(TR $(TD -$(INFIN)) $(TD +0.0) ) * $(TR $(TD $(NAN)) $(TD $(NAN)) ) @@ -1489,7 +1558,7 @@ unittest * than exp(x)-1. * * $(TABLE_SV - * $(TR $(TH x) $(TH e$(SUP x)-1) ) + * $(TR $(TH x) $(TH e$(SUPERSCRIPT x)-1) ) * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) ) * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) ) * $(TR $(TD -$(INFIN)) $(TD -1.0) ) @@ -1501,7 +1570,7 @@ real expm1(real x) @trusted pure nothrow @nogc version(D_InlineAsm_X86) { enum PARAMSIZE = (real.sizeof+3)&(0xFFFF_FFFC); // always a multiple of 4 - asm + asm pure nothrow @nogc { /* expm1() for x87 80-bit reals, IEEE754-2008 conformant. * Author: Don Clugston. @@ -1572,13 +1641,13 @@ L_largenegative: } else version(D_InlineAsm_X86_64) { - asm + asm pure nothrow @nogc { naked; } version (Win64) { - asm + asm pure nothrow @nogc { fld real ptr [RCX]; // x mov AX,[RCX+8]; // AX = exponent and sign @@ -1586,13 +1655,13 @@ L_largenegative: } else { - asm + asm pure nothrow @nogc { fld real ptr [RSP+8]; // x mov AX,[RSP+8+8]; // AX = exponent and sign } } - asm + asm pure nothrow @nogc { /* expm1() for x87 80-bit reals, IEEE754-2008 conformant. * Author: Don Clugston. @@ -1718,7 +1787,7 @@ L_largenegative: /** - * Calculates 2$(SUP x). + * Calculates 2$(SUPERSCRIPT x). * * $(TABLE_SV * $(TR $(TH x) $(TH exp2(x)) ) @@ -1733,7 +1802,7 @@ real exp2(real x) @nogc @trusted pure nothrow { enum PARAMSIZE = (real.sizeof+3)&(0xFFFF_FFFC); // always a multiple of 4 - asm + asm pure nothrow @nogc { /* exp2() for x87 80-bit reals, IEEE754-2008 conformant. * Author: Don Clugston. @@ -1817,13 +1886,13 @@ L_was_nan: } else version(D_InlineAsm_X86_64) { - asm + asm pure nothrow @nogc { naked; } version (Win64) { - asm + asm pure nothrow @nogc { fld real ptr [RCX]; // x mov AX,[RCX+8]; // AX = exponent and sign @@ -1831,13 +1900,13 @@ L_was_nan: } else { - asm + asm pure nothrow @nogc { fld real ptr [RSP+8]; // x mov AX,[RSP+8+8]; // AX = exponent and sign } } - asm + asm pure nothrow @nogc { /* exp2() for x87 80-bit reals, IEEE754-2008 conformant. * Author: Don Clugston. @@ -1961,11 +2030,16 @@ L_was_nan: } } +/// unittest { assert(feqrel(exp2(0.5L), SQRT2) >= real.mant_dig -1); assert(exp2(8.0L) == 256.0); assert(exp2(-9.0L)== 1.0L/512.0); +} + +unittest +{ version(CRuntime_Microsoft) {} else // aexp2/exp2f/exp2l not implemented { assert( core.stdc.math.exp2f(0.0f) == 1 ); @@ -2054,7 +2128,7 @@ creal expi(real y) @trusted pure nothrow @nogc { version (Win64) { - asm + asm pure nothrow @nogc { naked; fld real ptr [ECX]; @@ -2065,7 +2139,7 @@ creal expi(real y) @trusted pure nothrow @nogc } else { - asm + asm pure nothrow @nogc { fld y; fsincos; @@ -2079,7 +2153,8 @@ creal expi(real y) @trusted pure nothrow @nogc } } -unittest +/// +@safe pure nothrow @nogc unittest { assert(expi(1.3e5L) == cos(1.3e5L) + sin(1.3e5L) * 1i); assert(expi(0.0L) == 1L + 0.0Li); @@ -2090,7 +2165,7 @@ unittest * * Returns: * Calculate and return $(I x) and $(I exp) such that - * value =$(I x)*2$(SUP exp) and + * value =$(I x)*2$(SUPERSCRIPT exp) and * .5 $(LT)= |$(I x)| $(LT) 1.0 * * $(I x) has same sign as value. @@ -2103,12 +2178,16 @@ unittest * $(TR $(TD $(PLUSMN)$(NAN)) $(TD $(PLUSMN)$(NAN)) $(TD int.min)) * ) */ -real frexp(real value, out int exp) @trusted pure nothrow @nogc +T frexp(T)(T value, out int exp) @trusted pure nothrow @nogc + if(isFloatingPoint!T) { ushort* vu = cast(ushort*)&value; - long* vl = cast(long*)&value; + static if(is(Unqual!T == float)) + int* vi = cast(int*)&value; + else + long* vl = cast(long*)&value; int ex; - alias F = floatTraits!(real); + alias F = floatTraits!T; ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; static if (F.realFormat == RealFormat.ieeeExtended) @@ -2145,7 +2224,7 @@ real frexp(real value, out int exp) @trusted pure nothrow @nogc value *= F.RECIP_EPSILON; ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; - exp = ex - F.EXPBIAS - real.mant_dig + 1; + exp = ex - F.EXPBIAS - T.mant_dig + 1; vu[F.EXPPOS_SHORT] = (0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FFE; } return value; @@ -2187,7 +2266,7 @@ real frexp(real value, out int exp) @trusted pure nothrow @nogc // subnormal value *= F.RECIP_EPSILON; ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; - exp = ex - F.EXPBIAS - real.mant_dig + 1; + exp = ex - F.EXPBIAS - T.mant_dig + 1; vu[F.EXPPOS_SHORT] = cast(ushort)((0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FFE); } @@ -2227,84 +2306,128 @@ real frexp(real value, out int exp) @trusted pure nothrow @nogc // subnormal value *= F.RECIP_EPSILON; ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; - exp = ((ex - F.EXPBIAS)>> 4) - real.mant_dig + 1; + exp = ((ex - F.EXPBIAS) >> 4) - T.mant_dig + 1; vu[F.EXPPOS_SHORT] = cast(ushort)((0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FE0); } return value; } + else static if (F.realFormat == RealFormat.ieeeSingle) + { + if (ex) // If exponent is non-zero + { + if (ex == F.EXPMASK) // infinity or NaN + { + if (*vi == 0x7F80_0000) // positive infinity + { + exp = int.max; + } + else if (*vi == 0xFF80_0000) // negative infinity + exp = int.min; + else + { // NaN + *vi |= 0x0040_0000; // convert NaNS to NaNQ + exp = int.min; + } + } + else + { + exp = (ex - F.EXPBIAS) >> 7; + vu[F.EXPPOS_SHORT] = cast(ushort)((0x807F & vu[F.EXPPOS_SHORT]) | 0x3F00); + } + } + else if (!(*vi & 0x7FFF_FFFF)) + { + // value is +-0.0 + exp = 0; + } + else + { + // subnormal + value *= F.RECIP_EPSILON; + ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; + exp = ((ex - F.EXPBIAS) >> 7) - T.mant_dig + 1; + vu[F.EXPPOS_SHORT] = + cast(ushort)((0x8000 & vu[F.EXPPOS_SHORT]) | 0x3F00); + } + return value; + } else // static if (F.realFormat == RealFormat.ibmExtended) { assert (0, "frexp not implemented"); } } - +/// unittest { - static real[3][] vals = // x,frexp,exp - [ - [0.0, 0.0, 0], - [-0.0, -0.0, 0], - [1.0, .5, 1], - [-1.0, -.5, 1], - [2.0, .5, 2], - [double.min_normal/2.0, .5, -1022], - [real.infinity,real.infinity,int.max], - [-real.infinity,-real.infinity,int.min], - [real.nan,real.nan,int.min], - [-real.nan,-real.nan,int.min], - ]; - - int i; - - for (i = 0; i < vals.length; i++) - { - real x = vals[i][0]; - real e = vals[i][1]; - int exp = cast(int)vals[i][2]; - int eptr; - real v = frexp(x, eptr); - assert(isIdentical(e, v)); - assert(exp == eptr); + int exp; + real mantissa = frexp(123.456L, exp); - } + // check if values are equal to 19 decimal digits of precision + assert(equalsDigit(mantissa * pow(2.0L, cast(real)exp), 123.456L, 19)); - static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) - { - static real[3][] extendedvals = [ // x,frexp,exp - [0x1.a5f1c2eb3fe4efp+73L, 0x1.A5F1C2EB3FE4EFp-1L, 74], // normal - [0x1.fa01712e8f0471ap-1064L, 0x1.fa01712e8f0471ap-1L, -1063], - [real.min_normal, .5, -16381], - [real.min_normal/2.0L, .5, -16382] // subnormal - ]; + assert(frexp(-real.nan, exp) && exp == int.min); + assert(frexp(real.nan, exp) && exp == int.min); + assert(frexp(-real.infinity, exp) == -real.infinity && exp == int.min); + assert(frexp(real.infinity, exp) == real.infinity && exp == int.max); + assert(frexp(-0.0, exp) == -0.0 && exp == 0); + assert(frexp(0.0, exp) == 0.0 && exp == 0); +} - for (i = 0; i < extendedvals.length; i++) - { - real x = extendedvals[i][0]; - real e = extendedvals[i][1]; - int exp = cast(int)extendedvals[i][2]; +unittest +{ + import std.typetuple, std.typecons; + + foreach (T; TypeTuple!(real, double, float)) + { + Tuple!(T, T, int)[] vals = // x,frexp,exp + [ + tuple(T(0.0), T( 0.0 ), 0), + tuple(T(-0.0), T( -0.0), 0), + tuple(T(1.0), T( .5 ), 1), + tuple(T(-1.0), T( -.5 ), 1), + tuple(T(2.0), T( .5 ), 2), + tuple(T(float.min_normal/2.0f), T(.5), -126), + tuple(T.infinity, T.infinity, int.max), + tuple(-T.infinity, -T.infinity, int.min), + tuple(T.nan, T.nan, int.min), + tuple(-T.nan, -T.nan, int.min), + ]; + + foreach(elem; vals) + { + T x = elem[0]; + T e = elem[1]; + int exp = elem[2]; int eptr; - real v = frexp(x, eptr); + T v = frexp(x, eptr); assert(isIdentical(e, v)); assert(exp == eptr); } - } -} -unittest -{ - int exp; - real mantissa = frexp(123.456, exp); - assert(equalsDigit(mantissa * pow(2.0L, cast(real)exp), 123.456, 19)); + static if (floatTraits!(T).realFormat == RealFormat.ieeeExtended) + { + static T[3][] extendedvals = [ // x,frexp,exp + [0x1.a5f1c2eb3fe4efp+73L, 0x1.A5F1C2EB3FE4EFp-1L, 74], // normal + [0x1.fa01712e8f0471ap-1064L, 0x1.fa01712e8f0471ap-1L, -1063], + [T.min_normal, .5, -16381], + [T.min_normal/2.0L, .5, -16382] // subnormal + ]; + foreach(elem; extendedvals) + { + T x = elem[0]; + T e = elem[1]; + int exp = cast(int)elem[2]; + int eptr; + T v = frexp(x, eptr); + assert(isIdentical(e, v)); + assert(exp == eptr); - assert(frexp(-real.nan, exp) && exp == int.min); - assert(frexp(real.nan, exp) && exp == int.min); - assert(frexp(-real.infinity, exp) == -real.infinity && exp == int.min); - assert(frexp(real.infinity, exp) == real.infinity && exp == int.max); - assert(frexp(-0.0, exp) == -0.0 && exp == 0); - assert(frexp(0.0, exp) == 0.0 && exp == 0); + } + } + } } /****************************************** @@ -2324,7 +2447,7 @@ int ilogb(real x) @trusted nothrow @nogc { version (Win64_DMD_InlineAsm) { - asm + asm pure nothrow @nogc { naked ; fld real ptr [RCX] ; @@ -2357,7 +2480,7 @@ int ilogb(real x) @trusted nothrow @nogc else version (CRuntime_Microsoft) { int res; - asm + asm pure nothrow @nogc { naked ; fld real ptr [x] ; @@ -2395,13 +2518,30 @@ alias FP_ILOGBNAN = core.stdc.math.FP_ILOGBNAN; /******************************************* - * Compute n * 2$(SUP exp) + * Compute n * 2$(SUPERSCRIPT exp) * References: frexp */ real ldexp(real n, int exp) @nogc @safe pure nothrow; /* intrinsic */ -unittest +/// +@nogc @safe pure nothrow unittest +{ + real r; + + r = ldexp(3.0L, 3); + assert(r == 24); + + r = ldexp(cast(real) 3.0, cast(int) 3); + assert(r == 24); + + real n = 3.0; + int exp = 3; + r = ldexp(n, exp); + assert(r == 24); +} + +@safe pure nothrow @nogc unittest { static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) { @@ -2452,22 +2592,6 @@ unittest } } -unittest -{ - real r; - - r = ldexp(3.0L, 3); - assert(r == 24); - - r = ldexp(cast(real) 3.0, cast(int) 3); - assert(r == 24); - - real n = 3.0; - int exp = 3; - r = ldexp(n, exp); - assert(r == 24); -} - /************************************** * Calculate the natural logarithm of x. * @@ -2478,7 +2602,6 @@ unittest * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no) $(TD no)) * ) */ - real log(real x) @safe pure nothrow @nogc { version (INLINE_YL2X) @@ -2590,7 +2713,8 @@ real log(real x) @safe pure nothrow @nogc } } -unittest +/// +@safe pure nothrow @nogc unittest { assert(log(E) == 1); } @@ -2605,7 +2729,6 @@ unittest * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no) $(TD no)) * ) */ - real log10(real x) @safe pure nothrow @nogc { version (INLINE_YL2X) @@ -2721,9 +2844,9 @@ real log10(real x) @safe pure nothrow @nogc } } -unittest +/// +@safe pure nothrow @nogc unittest { - //printf("%Lg\n", log10(1000) - 3); assert(fabs(log10(1000) - 3) < .000001); } @@ -2741,7 +2864,6 @@ unittest * $(TR $(TD +$(INFIN)) $(TD -$(INFIN)) $(TD no) $(TD no)) * ) */ - real log1p(real x) @safe pure nothrow @nogc { version(INLINE_YL2X) @@ -2883,9 +3005,11 @@ real log2(real x) @safe pure nothrow @nogc } } +/// unittest { - assert(equalsDigit(log2(1024), 10, 19)); + // check if values are equal to 19 decimal digits of precision + assert(equalsDigit(log2(1024.0L), 10, 19)); } /***************************************** @@ -2894,7 +3018,7 @@ unittest * If x is subnormal, it is treated as if it were normalized. * For a positive, finite x: * - * 1 $(LT)= $(I x) * FLT_RADIX$(SUP -logb(x)) $(LT) FLT_RADIX + * 1 $(LT)= $(I x) * FLT_RADIX$(SUPERSCRIPT -logb(x)) $(LT) FLT_RADIX * * $(TABLE_SV * $(TR $(TH x) $(TH logb(x)) $(TH divide by 0?) ) @@ -2906,7 +3030,7 @@ real logb(real x) @trusted nothrow @nogc { version (Win64_DMD_InlineAsm) { - asm + asm pure nothrow @nogc { naked ; fld real ptr [RCX] ; @@ -2917,7 +3041,7 @@ real logb(real x) @trusted nothrow @nogc } else version (CRuntime_Microsoft) { - asm + asm pure nothrow @nogc { fld x ; fxtract ; @@ -2975,7 +3099,7 @@ real modf(real x, ref real i) @trusted nothrow @nogc } /************************************* - * Efficiently calculates x * 2$(SUP n). + * Efficiently calculates x * 2$(SUPERSCRIPT n). * * scalbn handles underflow and overflow in * the same fashion as the basic arithmetic operators. @@ -2989,10 +3113,10 @@ real modf(real x, ref real i) @trusted nothrow @nogc real scalbn(real x, int n) @trusted nothrow @nogc { version(InlineAsm_X86_Any) { - // scalbnl is not supported on DMD-Windows, so use asm. + // scalbnl is not supported on DMD-Windows, so use asm pure nothrow @nogc. version (Win64) { - asm { + asm pure nothrow @nogc { naked ; mov 16[RSP],RCX ; fild word ptr 16[RSP] ; @@ -3004,7 +3128,7 @@ real scalbn(real x, int n) @trusted nothrow @nogc } else { - asm { + asm pure nothrow @nogc { fild n; fld x; fscale; @@ -3018,7 +3142,8 @@ real scalbn(real x, int n) @trusted nothrow @nogc } } -unittest +/// +@safe nothrow @nogc unittest { assert(scalbn(-real.infinity, 5) == -real.infinity); } @@ -3172,7 +3297,7 @@ real ceil(real x) @trusted pure nothrow @nogc { version (Win64_DMD_InlineAsm) { - asm + asm pure nothrow @nogc { naked ; fld real ptr [RCX] ; @@ -3192,7 +3317,7 @@ real ceil(real x) @trusted pure nothrow @nogc else version(CRuntime_Microsoft) { short cw; - asm + asm pure nothrow @nogc { fld x ; fstcw cw ; @@ -3221,7 +3346,8 @@ real ceil(real x) @trusted pure nothrow @nogc } } -unittest +/// +@safe pure nothrow @nogc unittest { assert(ceil(+123.456L) == +124); assert(ceil(-123.456L) == -123); @@ -3249,7 +3375,7 @@ double ceil(double x) @trusted pure nothrow @nogc return y; } -unittest +@safe pure nothrow @nogc unittest { assert(ceil(+123.456) == +124); assert(ceil(-123.456) == -123); @@ -3277,7 +3403,7 @@ float ceil(float x) @trusted pure nothrow @nogc return y; } -unittest +@safe pure nothrow @nogc unittest { assert(ceil(+123.456f) == +124); assert(ceil(-123.456f) == -123); @@ -3299,7 +3425,7 @@ real floor(real x) @trusted pure nothrow @nogc { version (Win64_DMD_InlineAsm) { - asm + asm pure nothrow @nogc { naked ; fld real ptr [RCX] ; @@ -3319,7 +3445,7 @@ real floor(real x) @trusted pure nothrow @nogc else version(CRuntime_Microsoft) { short cw; - asm + asm pure nothrow @nogc { fld x ; fstcw cw ; @@ -3344,7 +3470,8 @@ real floor(real x) @trusted pure nothrow @nogc } } -unittest +/// +@safe pure nothrow @nogc unittest { assert(floor(+123.456L) == +123); assert(floor(-123.456L) == -124); @@ -3368,7 +3495,7 @@ double floor(double x) @trusted pure nothrow @nogc return floorImpl(x); } -unittest +@safe pure nothrow @nogc unittest { assert(floor(+123.456) == +123); assert(floor(-123.456) == -124); @@ -3392,7 +3519,7 @@ float floor(float x) @trusted pure nothrow @nogc return floorImpl(x); } -unittest +@safe pure nothrow @nogc unittest { assert(floor(+123.456f) == +123); assert(floor(-123.456f) == -124); @@ -3449,7 +3576,7 @@ long lrint(real x) @trusted pure nothrow @nogc { version (Win64) { - asm + asm pure nothrow @nogc { naked; fld real ptr [RCX]; @@ -3461,7 +3588,7 @@ long lrint(real x) @trusted pure nothrow @nogc else { long n; - asm + asm pure nothrow @nogc { fld x; fistp n; @@ -3578,7 +3705,8 @@ long lrint(real x) @trusted pure nothrow @nogc } } -unittest +/// +@safe pure nothrow @nogc unittest { assert(lrint(4.5) == 4); assert(lrint(5.5) == 6); @@ -3626,7 +3754,7 @@ long lround(real x) @trusted nothrow @nogc version(Posix) { - unittest + @safe nothrow @nogc unittest { assert(lround(0.49) == 0); assert(lround(0.5) == 1); @@ -3643,7 +3771,7 @@ real trunc(real x) @trusted nothrow @nogc { version (Win64_DMD_InlineAsm) { - asm + asm pure nothrow @nogc { naked ; fld real ptr [RCX] ; @@ -3663,7 +3791,7 @@ real trunc(real x) @trusted nothrow @nogc else version(CRuntime_Microsoft) { short cw; - asm + asm pure nothrow @nogc { fld x ; fstcw cw ; @@ -3730,29 +3858,6 @@ real remquo(real x, real y, out int n) @trusted nothrow @nogc /// ditto is inexact, or that a signalling NaN has been encountered. If floating-point exceptions are enabled (unmasked), a hardware exception will be generated instead of setting these flags. - - Example: - ---- - real a=3.5; - // Set all the flags to zero - resetIeeeFlags(); - assert(!ieeeFlags.divByZero); - // Perform a division by zero. - a/=0.0L; - assert(a==real.infinity); - assert(ieeeFlags.divByZero); - // Create a NaN - a*=0.0L; - assert(ieeeFlags.invalid); - assert(isNaN(a)); - - // Check that calling func() has no effect on the - // status flags. - IeeeFlags f = ieeeFlags; - func(); - assert(ieeeFlags == f); - - ---- */ struct IeeeFlags { @@ -3774,34 +3879,30 @@ private: // Don't bother about subnormals, they are not supported on most CPUs. // SUBNORMAL_MASK = 0x02; } - else version (PPC) + else version (PPC_Any) { // PowerPC FPSCR is a 32-bit register. enum : int { - INEXACT_MASK = 0x600, - UNDERFLOW_MASK = 0x010, - OVERFLOW_MASK = 0x008, - DIVBYZERO_MASK = 0x020, - INVALID_MASK = 0xF80 // PowerPC has five types of invalid exceptions. + INEXACT_MASK = 0x02000000, + DIVBYZERO_MASK = 0x04000000, + UNDERFLOW_MASK = 0x08000000, + OVERFLOW_MASK = 0x10000000, + INVALID_MASK = 0x20000000 // Summary as PowerPC has five types of invalid exceptions. } } - else version (PPC64) + else version (ARM) { - // PowerPC FPSCR is a 32-bit register. + // ARM FPSCR is a 32bit register enum : int { - INEXACT_MASK = 0x600, - UNDERFLOW_MASK = 0x010, - OVERFLOW_MASK = 0x008, - DIVBYZERO_MASK = 0x020, - INVALID_MASK = 0xF80 // PowerPC has five types of invalid exceptions. + INEXACT_MASK = 0x1000, + UNDERFLOW_MASK = 0x0800, + OVERFLOW_MASK = 0x0400, + DIVBYZERO_MASK = 0x0200, + INVALID_MASK = 0x0100 } } - else version (ARM) - { - // TODO: Fill this in for VFP. - } else version(SPARC) { // SPARC FSR is a 32bit register @@ -3822,7 +3923,7 @@ private: { version(D_InlineAsm_X86) { - asm + asm pure nothrow @nogc { fstsw AX; // NOTE: If compiler supports SSE2, need to OR the result with @@ -3833,7 +3934,7 @@ private: } else version(D_InlineAsm_X86_64) { - asm + asm pure nothrow @nogc { fstsw AX; // NOTE: If compiler supports SSE2, need to OR the result with @@ -3846,7 +3947,7 @@ private: { /* int retval; - asm { st %fsr, retval; } + asm pure nothrow @nogc { st %fsr, retval; } return retval; */ assert(0, "Not yet supported"); @@ -3862,7 +3963,7 @@ private: { version(InlineAsm_X86_Any) { - asm + asm pure nothrow @nogc { fnclex; } @@ -3871,9 +3972,9 @@ private: { /* SPARC: int tmpval; - asm { st %fsr, tmpval; } + asm pure nothrow @nogc { st %fsr, tmpval; } tmpval &=0xFFFF_FC00; - asm { ld tmpval, %fsr; } + asm pure nothrow @nogc { ld tmpval, %fsr; } */ assert(0, "Not yet supported"); } @@ -3899,6 +4000,34 @@ public: } } + +/// +unittest +{ + static void func() { + int a = 10 * 10; + } + + real a=3.5; + // Set all the flags to zero + resetIeeeFlags(); + assert(!ieeeFlags.divByZero); + // Perform a division by zero. + a/=0.0L; + assert(a==real.infinity); + assert(ieeeFlags.divByZero); + // Create a NaN + a*=0.0L; + assert(ieeeFlags.invalid); + assert(isNaN(a)); + + // Check that calling func() has no effect on the + // status flags. + IeeeFlags f = ieeeFlags; + func(); + assert(ieeeFlags == f); +} + version(X86_Any) { version = IeeeFlagsSupport; @@ -3983,6 +4112,16 @@ struct FloatingPointControl roundToZero = 0xC00000 } } + else version(PPC_Any) + { + enum : RoundingMode + { + roundToNearest = 0x00000000, + roundDown = 0x00000003, + roundUp = 0x00000002, + roundToZero = 0x00000001 + } + } else { enum : RoundingMode @@ -4014,6 +4153,22 @@ struct FloatingPointControl | inexactException | subnormalException, } } + else version(PPC_Any) + { + enum : uint + { + inexactException = 0x0008, + divByZeroException = 0x0010, + underflowException = 0x0020, + overflowException = 0x0040, + invalidException = 0x0080, + /// Severe = The overflow, division by zero, and invalid exceptions. + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } else { enum : uint @@ -4038,6 +4193,11 @@ private: enum uint EXCEPTION_MASK = 0x9F00; enum uint ROUNDING_MASK = 0xC00000; } + else version(PPC_Any) + { + enum uint EXCEPTION_MASK = 0x00F8; + enum uint ROUNDING_MASK = 0x0003; + } else version(X86) { enum ushort EXCEPTION_MASK = 0x3F; @@ -4055,9 +4215,9 @@ public: /// Returns true if the current FPU supports exception trapping @property static bool hasExceptionTraps() @safe nothrow @nogc { - version(X86) + version(X86_Any) return true; - else version(X86_64) + else version(PPC_Any) return true; else version(ARM) { @@ -4078,10 +4238,10 @@ public: { assert(hasExceptionTraps); initialize(); - version(ARM) - setControlState(getControlState() | (exceptions & EXCEPTION_MASK)); - else + version(X86_Any) setControlState(getControlState() & ~(exceptions & EXCEPTION_MASK)); + else + setControlState(getControlState() | (exceptions & EXCEPTION_MASK)); } /// Disable (mask) specific hardware exceptions. Multiple exceptions may be ORed together. @@ -4089,10 +4249,10 @@ public: { assert(hasExceptionTraps); initialize(); - version(ARM) - setControlState(getControlState() & ~(exceptions & EXCEPTION_MASK)); - else + version(X86_Any) setControlState(getControlState() | (exceptions & EXCEPTION_MASK)); + else + setControlState(getControlState() & ~(exceptions & EXCEPTION_MASK)); } //// Change the floating-point hardware rounding mode @@ -4106,10 +4266,10 @@ public: @property static uint enabledExceptions() @nogc { assert(hasExceptionTraps); - version(ARM) - return (getControlState() & EXCEPTION_MASK); - else + version(X86_Any) return (getControlState() & EXCEPTION_MASK) ^ EXCEPTION_MASK; + else + return (getControlState() & EXCEPTION_MASK); } /// Return the currently active rounding mode @@ -4135,6 +4295,10 @@ private: { alias ControlState = uint; } + else version(PPC_Any) + { + alias ControlState = uint; + } else { alias ControlState = ushort; @@ -4154,7 +4318,7 @@ private: { version (InlineAsm_X86_Any) { - asm + asm nothrow @nogc { fclex; } @@ -4164,12 +4328,12 @@ private: } // Read from the control register - static ushort getControlState() @trusted nothrow @nogc + static ControlState getControlState() @trusted nothrow @nogc { version (D_InlineAsm_X86) { short cont; - asm + asm nothrow @nogc { xor EAX, EAX; fstcw cont; @@ -4180,7 +4344,7 @@ private: version (D_InlineAsm_X86_64) { short cont; - asm + asm nothrow @nogc { xor RAX, RAX; fstcw cont; @@ -4192,13 +4356,13 @@ private: } // Set the control register - static void setControlState(ushort newState) @trusted nothrow @nogc + static void setControlState(ControlState newState) @trusted nothrow @nogc { version (InlineAsm_X86_Any) { version (Win64) { - asm + asm nothrow @nogc { naked; mov 8[RSP],RCX; @@ -4209,7 +4373,7 @@ private: } else { - asm + asm nothrow @nogc { fclex; fldcw newState; @@ -4262,7 +4426,6 @@ unittest /********************************* * Returns !=0 if e is a NaN. */ - bool isNaN(X)(X x) @nogc @trusted pure nothrow if (isFloatingPoint!(X)) { @@ -4299,6 +4462,17 @@ bool isNaN(X)(X x) @nogc @trusted pure nothrow } } +/// +@safe pure nothrow @nogc unittest +{ + assert( isNaN(float.init)); + assert( isNaN(-double.init)); + assert( isNaN(real.nan)); + assert( isNaN(-real.nan)); + assert(!isNaN(cast(float)53.6)); + assert(!isNaN(cast(real)-53.6)); +} + deprecated("isNaN is not defined for integer types") bool isNaN(X)(X x) @nogc @trusted pure nothrow if (isIntegral!(X)) @@ -4306,7 +4480,7 @@ bool isNaN(X)(X x) @nogc @trusted pure nothrow return isNaN(cast(float)x); } -unittest +@safe pure nothrow @nogc unittest { import std.typetuple; @@ -4342,7 +4516,6 @@ unittest /********************************* * Returns !=0 if e is finite (not infinite or $(NAN)). */ - int isFinite(X)(X e) @trusted pure nothrow @nogc { alias F = floatTraits!(X); @@ -4350,14 +4523,18 @@ int isFinite(X)(X e) @trusted pure nothrow @nogc return (pe[F.EXPPOS_SHORT] & F.EXPMASK) != F.EXPMASK; } -unittest +/// +@safe pure nothrow @nogc unittest { - assert(isFinite(1.23f)); - assert(isFinite(float.max)); - assert(isFinite(float.min_normal)); + assert( isFinite(1.23f)); + assert( isFinite(float.max)); + assert( isFinite(float.min_normal)); assert(!isFinite(float.nan)); assert(!isFinite(float.infinity)); +} +@safe pure nothrow @nogc unittest +{ assert(isFinite(1.23)); assert(isFinite(double.max)); assert(isFinite(double.min_normal)); @@ -4385,7 +4562,6 @@ int isFinite(X)(X x) @trusted pure nothrow @nogc /* Need one for each format because subnormal floats might * be converted to normal reals. */ - int isNormal(X)(X x) @trusted pure nothrow @nogc { alias F = floatTraits!(X); @@ -4401,8 +4577,8 @@ int isNormal(X)(X x) @trusted pure nothrow @nogc } } - -unittest +/// +@safe pure nothrow @nogc unittest { float f = 3; double d = 500; @@ -4424,12 +4600,10 @@ unittest /********************************* * Is number subnormal? (Also called "denormal".) * Subnormals have a 0 exponent and a 0 most significant mantissa bit. - */ - -/* Need one for each format because subnormal floats might + * + * Need one for each format because subnormal floats might * be converted to normal reals. */ - int isSubnormal(X)(X x) @trusted pure nothrow @nogc { alias F = floatTraits!(X); @@ -4468,7 +4642,8 @@ int isSubnormal(X)(X x) @trusted pure nothrow @nogc } } -unittest +/// +@safe pure nothrow @nogc unittest { import std.typetuple; @@ -4490,7 +4665,6 @@ int isSubnormal(X)(X x) @trusted pure nothrow @nogc /********************************* * Return !=0 if e is $(PLUSMN)$(INFIN). */ - bool isInfinity(X)(X x) @nogc @trusted pure nothrow if (isFloatingPoint!(X)) { @@ -4529,9 +4703,9 @@ bool isInfinity(X)(X x) @nogc @trusted pure nothrow } } -unittest +/// +@nogc @safe pure nothrow unittest { - // CTFE-able tests assert(!isInfinity(float.init)); assert(!isInfinity(-float.init)); assert(!isInfinity(float.nan)); @@ -4539,7 +4713,11 @@ unittest assert(isInfinity(float.infinity)); assert(isInfinity(-float.infinity)); assert(isInfinity(-1.0f / 0.0f)); +} +@safe pure nothrow @nogc unittest +{ + // CTFE-able tests assert(!isInfinity(double.init)); assert(!isInfinity(-double.init)); assert(!isInfinity(double.nan)); @@ -4603,7 +4781,6 @@ unittest * Same as ==, except that positive and negative zero are not identical, * and two $(NAN)s are identical if they have the same 'payload'. */ - bool isIdentical(real x, real y) @trusted pure nothrow @nogc { // We're doing a bitwise comparison so the endianness is irrelevant. @@ -4630,14 +4807,14 @@ bool isIdentical(real x, real y) @trusted pure nothrow @nogc /********************************* * Return 1 if sign bit of e is set, 0 if not. */ - int signbit(X)(X x) @nogc @trusted pure nothrow { alias F = floatTraits!(X); return ((cast(ubyte *)&x)[F.SIGNPOS_BYTE] & 0x80) != 0; } -unittest +/// +@nogc @safe pure nothrow unittest { debug (math) printf("math.signbit.unittest\n"); assert(!signbit(float.nan)); @@ -4679,7 +4856,6 @@ int signbit(X)(X x) @nogc @trusted pure nothrow /********************************* * Return a value composed of to with from's sign bit. */ - R copysign(R, X)(R to, X from) @trusted pure nothrow @nogc if (isFloatingPoint!(R) && isFloatingPoint!(X)) { @@ -4700,14 +4876,14 @@ R copysign(R, X)(X to, R from) @trusted pure nothrow @nogc return copysign(cast(R)to, from); } -unittest +@safe pure nothrow @nogc unittest { import std.typetuple; foreach (X; TypeTuple!(float, double, real, int, long)) { foreach (Y; TypeTuple!(float, double, real)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 X x = 21; Y y = 23.8; Y e = void; @@ -4732,7 +4908,7 @@ unittest e = copysign(X.nan, -y); assert(isNaN(e) && signbit(e)); } - } + }(); } } @@ -4753,9 +4929,9 @@ F sgn(F)(F x) @safe pure nothrow @nogc return x > 0 ? 1 : x < 0 ? -1 : x; } -unittest +/// +@safe pure nothrow @nogc unittest { - debug (math) printf("math.sgn.unittest\n"); assert(sgn(168.1234) == 1); assert(sgn(-168.1234) == -1); assert(sgn(0.0) == 0); @@ -4843,7 +5019,7 @@ real NaN(ulong payload) @trusted pure nothrow @nogc } } -unittest +@safe pure nothrow @nogc unittest { static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) { @@ -4906,7 +5082,7 @@ ulong getNaNPayload(real x) @trusted pure nothrow @nogc debug(UnitTest) { - unittest + @safe pure nothrow @nogc unittest { real nan4 = NaN(0x789_ABCD_EF12_3456); static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended @@ -5134,12 +5310,13 @@ float nextDown(float x) @safe pure nothrow @nogc return -nextUp(-x); } -unittest +/// +@safe pure nothrow @nogc unittest { assert( nextDown(1.0 + real.epsilon) == 1.0); } -unittest +@safe pure nothrow @nogc unittest { static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) { @@ -5228,7 +5405,8 @@ T nextafter(T)(T x, T y) @safe pure nothrow @nogc return ((y>x) ? nextUp(x) : nextDown(x)); } -unittest +/// +@safe pure nothrow @nogc unittest { float a = 1; assert(is(typeof(nextafter(a, a)) == float)); @@ -5275,7 +5453,7 @@ real fmin(real x, real y) @safe pure nothrow @nogc { return x < y ? x : y; } real fma(real x, real y, real z) @safe pure nothrow @nogc { return (x * y) + z; } /******************************************************************* - * Compute the value of x $(SUP n), where n is an integer + * Compute the value of x $(SUPERSCRIPT n), where n is an integer */ Unqual!F pow(F, G)(F x, G n) @nogc @trusted pure nothrow if (isFloatingPoint!(F) && isIntegral!(G)) @@ -5324,7 +5502,7 @@ Unqual!F pow(F, G)(F x, G n) @nogc @trusted pure nothrow return p; } -unittest +@safe pure nothrow @nogc unittest { // Make sure it instantiates and works properly on immutable values and // with various integer and float types. @@ -5379,7 +5557,6 @@ unittest * If n is negative, an integer divide error will occur at runtime, * regardless of the value of x. */ - typeof(Unqual!(F).init * Unqual!(G).init) pow(F, G)(F x, G n) @nogc @trusted pure nothrow if (isIntegral!(F) && isIntegral!(G)) { @@ -5417,7 +5594,8 @@ typeof(Unqual!(F).init * Unqual!(G).init) pow(F, G)(F x, G n) @nogc @trusted pur return p; } -unittest +/// +@safe pure nothrow @nogc unittest { immutable int one = 1; immutable byte two = 2; @@ -5442,7 +5620,7 @@ real pow(I, F)(I x, F y) @nogc @trusted pure nothrow } /********************************************* - * Calculates x$(SUP y). + * Calculates x$(SUPERSCRIPT y). * * $(TABLE_SV * $(TR $(TH x) $(TH y) $(TH pow(x, y)) @@ -5483,7 +5661,6 @@ real pow(I, F)(I x, F y) @nogc @trusted pure nothrow * $(TD no) $(TD no) ) * ) */ - Unqual!(Largest!(F, G)) pow(F, G)(F x, G y) @nogc @trusted pure nothrow if (isFloatingPoint!(F) && isFloatingPoint!(G)) { @@ -5667,7 +5844,7 @@ Unqual!(Largest!(F, G)) pow(F, G)(F x, G y) @nogc @trusted pure nothrow return impl(x, y); } -unittest +@safe pure nothrow @nogc unittest { // Test all the special values. These unittests can be run on Windows // by temporarily changing the version(linux) to version(all). @@ -5836,7 +6013,7 @@ int feqrel(X)(X x, X y) @trusted pure nothrow @nogc } } -unittest +@safe pure nothrow @nogc unittest { void testFeqrel(F)() { @@ -6012,7 +6189,7 @@ body return u; } -unittest +@safe pure nothrow @nogc unittest { assert(ieeeMean(-0.0,-1e-20)<0); assert(ieeeMean(0.0,1e-20)>0); @@ -6049,7 +6226,53 @@ public: * x = the value to evaluate. * A = array of coefficients $(SUB a, 0), $(SUB a, 1), etc. */ -real poly(real x, const real[] A) @trusted pure nothrow @nogc +Unqual!(CommonType!(T1, T2)) poly(T1, T2)(T1 x, in T2[] A) @trusted pure nothrow @nogc + if (isFloatingPoint!T1 && isFloatingPoint!T2) +in +{ + assert(A.length > 0); +} +body +{ + static if(is(Unqual!T2 == real)) + { + return polyImpl(x, A); + } + else + { + ptrdiff_t i = A.length - 1; + typeof(return) r = A[i]; + while (--i >= 0) + { + r *= x; + r += A[i]; + } + return r; + } +} + +/// +@safe nothrow @nogc unittest +{ + double x = 3.1; + static real[] pp = [56.1, 32.7, 6]; + + assert(poly(x, pp) == (56.1L + (32.7L + 6L * x) * x)); +} + +@safe nothrow @nogc unittest +{ + double x = 3.1; + static double[] pp = [56.1, 32.7, 6]; + double y = x; + y *= 6.0; + y += 32.7; + y *= x; + y += 56.1; + assert(poly(x, pp) == y); +} + +private real polyImpl(real x, in real[] A) @trusted pure nothrow @nogc in { assert(A.length > 0); @@ -6061,7 +6284,7 @@ body version (Windows) { // BUG: This code assumes a frame pointer in EBP. - asm // assembler by W. Bright + asm pure nothrow @nogc // assembler by W. Bright { // EDX = (A.length - 1) * real.sizeof mov ECX,A[EBP] ; // ECX = A.length @@ -6089,7 +6312,7 @@ body } else version (linux) { - asm // assembler by W. Bright + asm pure nothrow @nogc // assembler by W. Bright { // EDX = (A.length - 1) * real.sizeof mov ECX,A[EBP] ; // ECX = A.length @@ -6117,7 +6340,7 @@ body } else version (OSX) { - asm // assembler by W. Bright + asm pure nothrow @nogc // assembler by W. Bright { // EDX = (A.length - 1) * real.sizeof mov ECX,A[EBP] ; // ECX = A.length @@ -6145,7 +6368,7 @@ body } else version (FreeBSD) { - asm // assembler by W. Bright + asm pure nothrow @nogc // assembler by W. Bright { // EDX = (A.length - 1) * real.sizeof mov ECX,A[EBP] ; // ECX = A.length @@ -6173,7 +6396,7 @@ body } else version (Android) { - asm // assembler by W. Bright + asm pure nothrow @nogc // assembler by W. Bright { // EDX = (A.length - 1) * real.sizeof mov ECX,A[EBP] ; // ECX = A.length @@ -6217,14 +6440,6 @@ body } } -unittest -{ - debug (math) printf("math.poly.unittest\n"); - real x = 3.1; - static real[] pp = [56.1, 32.7, 6]; - - assert( poly(x, pp) == (56.1L + (32.7L + 6L * x) * x) ); -} /** Computes whether $(D lhs) is approximately equal to $(D rhs) @@ -6296,7 +6511,8 @@ bool approxEqual(T, U)(T lhs, U rhs) return approxEqual(lhs, rhs, 1e-2, 1e-5); } -unittest +/// +@safe pure nothrow unittest { assert(approxEqual(1.0, 1.0099)); assert(!approxEqual(1.0, 1.011)); @@ -6313,11 +6529,11 @@ unittest } // Included for backwards compatibility with Phobos1 -alias isnan = isNaN; -alias isfinite = isFinite; -alias isnormal = isNormal; -alias issubnormal = isSubnormal; -alias isinf = isInfinity; +deprecated("Phobos1 math functions are deprecated, use isNaN") alias isnan = isNaN; +deprecated("Phobos1 math functions are deprecated, use isFinite ") alias isfinite = isFinite; +deprecated("Phobos1 math functions are deprecated, use isNormal ") alias isnormal = isNormal; +deprecated("Phobos1 math functions are deprecated, use isSubnormal ") alias issubnormal = isSubnormal; +deprecated("Phobos1 math functions are deprecated, use isInfinity ") alias isinf = isInfinity; /* ********************************** * Building block functions, they @@ -6327,7 +6543,7 @@ alias isinf = isInfinity; real yl2x(real x, real y) @nogc @safe pure nothrow; // y * log2(x) real yl2xp1(real x, real y) @nogc @safe pure nothrow; // y * log2(x + 1) -unittest +@safe pure nothrow @nogc unittest { version (INLINE_YL2X) { @@ -6336,7 +6552,7 @@ unittest } } -unittest +@safe pure nothrow @nogc unittest { real num = real.infinity; assert(num == real.infinity); // Passes. @@ -6344,7 +6560,7 @@ unittest } -unittest +@safe pure nothrow @nogc unittest { float f = sqrt(2.0f); assert(fabs(f * f - 2.0f) < .00001); @@ -6356,7 +6572,7 @@ unittest assert(fabs(r * r - 2.0) < .00001); } -unittest +@safe pure nothrow @nogc unittest { float f = fabs(-2.0f); assert(f == 2); @@ -6368,8 +6584,7 @@ unittest assert(r == 2); } - -unittest +@safe pure nothrow @nogc unittest { float f = sin(-2.0f); assert(fabs(f - -0.909297f) < .00001); @@ -6381,8 +6596,7 @@ unittest assert(fabs(r - -0.909297f) < .00001); } - -unittest +@safe pure nothrow @nogc unittest { float f = cos(-2.0f); assert(fabs(f - -0.416147f) < .00001); @@ -6394,8 +6608,7 @@ unittest assert(fabs(r - -0.416147f) < .00001); } - -unittest +@safe pure nothrow @nogc unittest { float f = tan(-2.0f); assert(fabs(f - 2.18504f) < .00001); diff --git a/std/mathspecial.d b/std/mathspecial.d index d8ba22ac683..9d4087ca744 100644 --- a/std/mathspecial.d +++ b/std/mathspecial.d @@ -50,7 +50,7 @@ * * Copyright: Based on the CEPHES math library, which is * Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com). - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Stephen L. Moshier (original C code). Conversion to D by Don Clugston * Source: $(PHOBOSSRC std/_mathspecial.d) */ @@ -174,6 +174,15 @@ real digamma(real x) return std.internal.math.gammafunction.digamma(x); } +/** Log Minus Digamma function + * + * logmdigamma(x) = log(x) - digamma(x) + */ +real logmdigamma(real x) +{ + return std.internal.math.gammafunction.logmdigamma(x); +} + /** Incomplete beta integral * * Returns incomplete beta integral of the arguments, evaluated diff --git a/std/metastrings.d b/std/metastrings.d new file mode 100644 index 00000000000..ce2d6146f05 --- /dev/null +++ b/std/metastrings.d @@ -0,0 +1,3 @@ +// Explicitly undocumented. It will be removed in April 2015. +deprecated("Please use std.string.format, std.conv.to or std.conv.parse instead") +module std.metastrings; diff --git a/std/mmfile.d b/std/mmfile.d index 46cbca009b6..e619320e299 100644 --- a/std/mmfile.d +++ b/std/mmfile.d @@ -6,7 +6,7 @@ * WIKI=Phobos/StdMmfile * * Copyright: Copyright Digital Mars 2004 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright), * Matthew Wilson * Source: $(PHOBOSSRC std/_mmfile.d) @@ -32,8 +32,9 @@ import std.internal.cstring; version (Windows) { - private import std.c.windows.windows; + private import core.sys.windows.windows; private import std.utf; + private import std.windows.syserror; } else version (Posix) { @@ -220,7 +221,7 @@ class MmFile assert(0); } - if (filename.ptr) + if (filename != null) { hFile = CreateFileW(filename.tempCStringW(), dwDesiredAccess2, @@ -229,50 +230,36 @@ class MmFile dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, cast(HANDLE)null); + wenforce(hFile != INVALID_HANDLE_VALUE, "CreateFileW"); } else - hFile = null; + hFile = INVALID_HANDLE_VALUE; + scope(failure) if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); - hFileMap = null; - if (hFile != INVALID_HANDLE_VALUE) - { - int hi = cast(int)(size>>32); - hFileMap = CreateFileMappingW(hFile, null, flProtect, - hi, cast(uint)size, null); - } - if (hFileMap != null) // mapping didn't fail - { - - if (size == 0) - { - uint sizehi; - uint sizelow = GetFileSize(hFile,&sizehi); - size = (cast(ulong)sizehi << 32) + sizelow; - } - this.size = size; - - size_t initial_map = (window && 2*window>32); + hFileMap = CreateFileMappingW(hFile, null, flProtect, + hi, cast(uint)size, null); + wenforce(hFileMap, "CreateFileMapping"); + scope(failure) CloseHandle(hFileMap); - debug (MMFILE) printf("MmFile.this(): p = %p, size = %d\n", p, size); - return; - } + if (size == 0 && filename != null) + { + uint sizehi; + uint sizelow = GetFileSize(hFile, &sizehi); + wenforce(sizelow != INVALID_FILE_SIZE || GetLastError() != ERROR_SUCCESS, + "GetFileSize"); + size = (cast(ulong)sizehi << 32) + sizelow; } + this.size = size; - if (hFileMap != null) - CloseHandle(hFileMap); - hFileMap = null; - - if (hFile != INVALID_HANDLE_VALUE) - CloseHandle(hFile); - hFile = INVALID_HANDLE_VALUE; + size_t initial_map = (window && 2*window>32); p = MapViewOfFileEx(hFileMap, dwDesiredAccess, hi, cast(uint)start, len, address); - errnoEnforce(p); + wenforce(p, "MapViewOfFileEx"); } else { p = mmap(address, len, prot, flags, fd, cast(off_t)start); errnoEnforce(p != MAP_FAILED); @@ -607,7 +594,7 @@ unittest const size_t K = 1024; size_t win = 64*K; // assume the page size is 64K version(Windows) { - /+ these aren't defined in std.c.windows.windows so let's use default + /+ these aren't defined in core.sys.windows.windows so let's use default SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); win = sysinfo.dwAllocationGranularity; diff --git a/std/net/curl.d b/std/net/curl.d index e8f6c920f98..fd4644dfe87 100644 --- a/std/net/curl.d +++ b/std/net/curl.d @@ -1,8 +1,13 @@ // Written in the D programming language. /** - +Networking client functionality as provided by $(WEB _curl.haxx.se/libcurl, +libcurl). The libcurl library must be installed on the system in order to use +this module. + +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) @@ -15,10 +20,11 @@ $(TR $(TDNW Low level) $(TD $(MYREF HTTP) $(MYREF FTP) $(MYREF SMTP) ) ) ) +) -Networking client functionality as provided by $(WEB _curl.haxx.se/libcurl, -libcurl). The libcurl library must be installed on the system in order to use -this module. +Note: +You may need to link to the $(B curl) library, e.g. by adding $(D "libs": ["curl"]) +to your $(B dub.json) file if you are using $(LINK2 http://code.dlang.org, DUB). Windows x86 note: A DMD compatible libcurl static library can be downloaded from the dlang.org @@ -133,13 +139,10 @@ the onReceive callback. See $(LREF onReceiveHeader)/$(LREF onReceive) for more information. Finally the HTTP request is effected by calling perform(), which is synchronous. -Macros: -MYREF = $1  - Source: $(PHOBOSSRC std/net/_curl.d) Copyright: Copyright Jonas Drewsen 2011-2012 -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: Jonas Drewsen. Some of the SMTP code contributed by Jimmy Cao. Credits: The functionally is based on $(WEB _curl.haxx.se/libcurl, libcurl). @@ -387,6 +390,10 @@ unittest * Returns: * A T[] range containing the content of the resource pointed to by the URL. * + * Throws: + * + * $(D CurlException) on error. + * * See_Also: $(LREF HTTP.Method) */ T[] get(Conn = AutoProtocol, T = char)(const(char)[] url, Conn conn = Conn()) @@ -899,7 +906,7 @@ private auto _decodeContent(T)(ubyte[] content, string encoding) } } -alias KeepTerminator = std.string.KeepTerminator; +alias KeepTerminator = Flag!"keepTerminator"; /++ struct ByLineBuffer(Char) { @@ -1769,7 +1776,7 @@ private mixin template Protocol() } /** Sets whether SSL peer certificates should be verified. - See: $(WEB http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYPEER, verifypeer) + See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYPEER, verifypeer) */ @property void verifyPeer(bool on) { @@ -1777,7 +1784,7 @@ private mixin template Protocol() } /** Sets whether the host within an SSL certificate should be verified. - See: $(WEB http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYHOST, verifypeer) + See: $(WEB curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYHOST, verifypeer) */ @property void verifyHost(bool on) { @@ -1819,6 +1826,22 @@ private mixin template Protocol() http.perform(); } + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password) + { + p.curl.set(CurlOption.proxyuserpwd, + format("%s:%s", + username.replace(":", "%3A"), + password.replace(":", "%3A")) + ); + } + /** * The event handler that gets called when data is needed for sending. The * length of the $(D void[]) specifies the maximum number of bytes that can @@ -2216,13 +2239,11 @@ struct HTTP After the HTTP client has been setup and possibly assigned callbacks the $(D perform()) method will start performing the request towards the specified server. - */ - void perform() - { - _perform(); - } - private CurlCode _perform(bool throwOnError = true) + Params: + throwOnError = whether to throw an exception or return a CurlCode on error + */ + CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes) { p.status.reset(); @@ -2390,6 +2411,15 @@ struct HTTP void setAuthentication(const(char)[] username, const(char)[] password, const(char)[] domain = ""); + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password); + /** * The event handler that gets called when data is needed for sending. The * length of the $(D void[]) specifies the maximum number of bytes that can @@ -2568,7 +2598,7 @@ struct HTTP return p.status; } - // Set the active cookie string e.g. "name1=value1;name2=value2" + /// Set the active cookie string e.g. "name1=value1;name2=value2" void setCookie(const(char)[] cookie) { p.curl.set(CurlOption.cookie, cookie); @@ -2914,13 +2944,11 @@ struct FTP After a FTP client has been setup and possibly assigned callbacks the $(D perform()) method will start performing the actual communication with the server. - */ - void perform() - { - _perform(); - } - private CurlCode _perform(bool throwOnError = true) + Params: + throwOnError = whether to throw an exception or return a CurlCode on error + */ + CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes) { return p.curl.perform(throwOnError); } @@ -3051,6 +3079,15 @@ struct FTP void setAuthentication(const(char)[] username, const(char)[] password, const(char)[] domain = ""); + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password); + /** * The event handler that gets called when data is needed for sending. The * length of the $(D void[]) specifies the maximum number of bytes that can @@ -3239,10 +3276,12 @@ struct SMTP /** Performs the request as configured. + Params: + throwOnError = whether to throw an exception or return a CurlCode on error */ - void perform() + CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes) { - p.curl.perform(); + return p.curl.perform(throwOnError); } /// The URL to specify the location of the resource. @@ -3389,6 +3428,15 @@ struct SMTP void setAuthentication(const(char)[] username, const(char)[] password, const(char)[] domain = ""); + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password); + /** * The event handler that gets called when data is needed for sending. The * length of the $(D void[]) specifies the maximum number of bytes that can @@ -3513,6 +3561,10 @@ class CurlTimeoutException : CurlException /// Equal to $(ECXREF curl, CURLcode) alias CurlCode = CURLcode; +import std.typecons : Flag; +/// Flag to specify whether or not an exception is thrown on error. +alias ThrowOnError = Flag!"throwOnError"; + /** Wrapper to provide a better interface to libcurl than using the plain C API. It is recommended to use the $(D HTTP)/$(D FTP) etc. structs instead unless @@ -3745,8 +3797,11 @@ struct Curl /** perform the curl request by doing the HTTP,FTP etc. as it has been setup beforehand. + + Params: + throwOnError = whether to throw an exception or return a CurlCode on error */ - CurlCode perform(bool throwOnError = true) + CurlCode perform(ThrowOnError throwOnError = ThrowOnError.yes) { throwOnStopped(); CurlCode code = curl_easy_perform(this.handle); @@ -3755,6 +3810,13 @@ struct Curl return code; } + // Explicitly undocumented. It will be removed in November 2015. + deprecated("Pass ThrowOnError.yes or .no instead of a boolean.") + CurlCode perform(bool throwOnError) + { + return perform(cast(ThrowOnError)throwOnError); + } + /** * The event handler that receives incoming data. * @@ -4353,7 +4415,7 @@ private static void _spawnAsync(Conn, Unit, Terminator = void)() CurlCode code; try { - code = client._perform(false); + code = client.perform(ThrowOnError.no); } catch (Exception ex) { diff --git a/std/net/isemail.d b/std/net/isemail.d index eae1c3c926a..10c5ce76700 100644 --- a/std/net/isemail.d +++ b/std/net/isemail.d @@ -24,17 +24,9 @@ */ module std.net.isemail; -import std.algorithm : cmp, equal, uniq, filter, contains = canFind; -import std.range : ElementType; -import std.array; -import std.ascii; -import std.conv; -import std.exception : enforce; -import std.regex; -import std.string; +// FIXME +import std.range.primitives; // : ElementType; import std.traits; -import std.utf; -import std.uni; /** * Check that an email address conforms to RFCs 5321, 5322 and others. @@ -67,6 +59,14 @@ import std.uni; EmailStatus isEmail (Char) (const(Char)[] email, CheckDns checkDNS = CheckDns.no, EmailStatusCode errorLevel = EmailStatusCode.none) if (isSomeChar!(Char)) { + import std.algorithm : uniq, canFind; + import std.exception : enforce; + import std.array : array, split; + import std.conv : to; + import std.regex : match, regex; + import std.string : indexOf, lastIndexOf; + import std.uni : isNumber; + alias tstring = const(Char)[]; enum defaultThreshold = 16; @@ -239,7 +239,7 @@ EmailStatus isEmail (Char) (const(Char)[] email, CheckDns checkDNS = CheckDns.no contextPrior = context; auto c = token.front; - if (c < '!' || c > '~' || c == '\n' || Token.specials.contains(token)) + if (c < '!' || c > '~' || c == '\n' || Token.specials.canFind(token)) returnStatus ~= EmailStatusCode.errorExpectingText; parseData[EmailPart.componentLocalPart] ~= token; @@ -354,7 +354,7 @@ EmailStatus isEmail (Char) (const(Char)[] email, CheckDns checkDNS = CheckDns.no auto c = token.front; hyphenFlag = false; - if (c < '!' || c > '~' || Token.specials.contains(token)) + if (c < '!' || c > '~' || Token.specials.canFind(token)) returnStatus ~= EmailStatusCode.errorExpectingText; else if (token == Token.hyphen) @@ -731,7 +731,7 @@ EmailStatus isEmail (Char) (const(Char)[] email, CheckDns checkDNS = CheckDns.no if (elementCount == 0) returnStatus ~= EmailStatusCode.rfc5321TopLevelDomain; - if (isNumeric(atomList[EmailPart.componentDomain][elementCount].front)) + if (isNumber(atomList[EmailPart.componentDomain][elementCount].front)) returnStatus ~= EmailStatusCode.rfc5321TopLevelDomainNumeric; } @@ -1269,38 +1269,39 @@ struct EmailStatus } /// Indicates if the email address is valid or not. - @property bool valid () + @property bool valid () const { return valid_; } /// The local part of the email address, that is, the part before the @ sign. - @property string localPart () + @property string localPart () const { return localPart_; } /// The domain part of the email address, that is, the part after the @ sign. - @property string domainPart () + @property string domainPart () const { return domainPart_; } /// The email status code - @property EmailStatusCode statusCode () + @property EmailStatusCode statusCode () const { return statusCode_; } /// Returns a describing string of the status code - @property string status () + @property string status () const { return statusCodeDescription(statusCode_); } /// Returns a textual representation of the email status - string toString () + string toString () const { + import std.format : format; return format("EmailStatus\n{\n\tvalid: %s\n\tlocalPart: %s\n\tdomainPart: %s\n\tstatusCode: %s\n}", valid, localPart, domainPart, statusCode); } @@ -1882,6 +1883,8 @@ unittest int compareFirstN (alias pred = "a < b", S1, S2) (S1 s1, S2 s2, size_t length, bool caseInsensitive = false) if (is(Unqual!(ElementType!(S1)) == dchar) && is(Unqual!(ElementType!(S2)) == dchar)) { + import std.uni : icmp; + import std.algorithm : cmp; auto s1End = length <= s1.length ? length : s1.length; auto s2End = length <= s2.length ? length : s2.length; @@ -1919,6 +1922,8 @@ unittest */ auto grep (Range, Regex) (Range input, Regex pattern, bool invert = false) { + import std.regex : match; + import std.algorithm : filter; auto dg = invert ? (ElementType!(Range) e) { return e.match(pattern).empty; } : (ElementType!(Range) e) { return !e.match(pattern).empty; }; @@ -1927,6 +1932,8 @@ auto grep (Range, Regex) (Range input, Regex pattern, bool invert = false) unittest { + import std.algorithm : equal; + import std.regex; assert(equal(["ab", "0a", "cd", "1b"].grep(regex(`\d\w`)), ["0a", "1b"])); assert(equal(["abc", "0123", "defg", "4567"].grep(regex(`4567`), true), ["abc", "0123", "defg"])); } @@ -1983,6 +1990,7 @@ unittest */ const(T)[] get (T) (const(T)[] str, size_t index, dchar c) { + import std.utf : codeLength; return str[index .. index + codeLength!(T)(c)]; } @@ -1991,83 +1999,3 @@ unittest assert("abc".get(1, 'b') == "b"); assert("löv".get(1, 'ö') == "ö"); } - -// issue 4673 -bool isNumeric (dchar c) -{ - switch (c) - { - case 'i': - case '.': - case '-': - case '+': - case 'u': - case 'l': - case 'L': - case 'U': - case 'I': - return false; - - default: - } - - return std.uni.isNumber(c); -} - -// Issue 5744 -import core.stdc.string : memcmp; - -ptrdiff_t lastIndexOf(Char1, Char2)(in Char1[] s, const(Char2)[] sub, - CaseSensitive cs = CaseSensitive.yes) if (isSomeChar!Char1 && isSomeChar!Char2) -{ - if (cs == CaseSensitive.yes) - { - Char2 c; - - if (sub.length == 0) - return s.length; - c = sub[0]; - if (sub.length == 1) - return std.string.lastIndexOf(s, c); - for (ptrdiff_t i = s.length - sub.length; i >= 0; i--) - { - if (s[i] == c) - { - if (memcmp(&s[i + 1], &sub[1], sub.length - 1) == 0) - return i; - } - } - return -1; - } - else - { - dchar c; - - if (sub.length == 0) - return s.length; - c = sub[0]; - if (sub.length == 1) - return std.string.lastIndexOf(s, c, cs); - if (c <= 0x7F) - { - c = std.ascii.toLower(c); - for (ptrdiff_t i = s.length - sub.length; i >= 0; i--) - { - if (std.ascii.toLower(s[i]) == c) - { - if (icmp(s[i + 1 .. i + sub.length], sub[1 .. sub.length]) == 0) - return i; - } - } - } - else - { - for (ptrdiff_t i = s.length - sub.length; i >= 0; i--) - { - if (icmp(s[i .. i + sub.length], sub) == 0) - return i; - } - } - return -1; - } -} diff --git a/std/numeric.d b/std/numeric.d index 1d07fb572d3..8f23ba698f5 100644 --- a/std/numeric.d +++ b/std/numeric.d @@ -10,7 +10,7 @@ Macros: WIKI = Phobos/StdNumeric Copyright: Copyright Andrei Alexandrescu 2008 - 2009. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(WEB erdani.org, Andrei Alexandrescu), Don Clugston, Robert Jacques, Ilya Yaroshenko (summation) Source: $(PHOBOSSRC std/_numeric.d) @@ -23,32 +23,20 @@ Distributed under the Boost Software License, Version 1.0. */ module std.numeric; -import std.algorithm; -import std.array; -import std.bitmanip; -import std.conv; -import std.typecons; +import std.complex; +import std.exception; import std.math; +import std.range.primitives; import std.traits; -import std.exception; -import std.random; -import std.string; -import std.range; -import std.c.stdlib; -import std.functional; -import std.typetuple; -import std.complex; - -import core.bitop; -import core.exception; +import std.typecons; version(unittest) { import std.stdio; } /// Format flags for CustomFloat. -public enum CustomFloatFlags { - +public enum CustomFloatFlags +{ /// Adds a sign bit to allow for signed numbers. signed = 1, @@ -95,10 +83,15 @@ public enum CustomFloatFlags { } // 64-bit version of core.bitop.bsr -private int bsr64(ulong value) { - union Ulong { +private int bsr64(ulong value) +{ + import core.bitop : bsr; + + union Ulong + { ulong raw; - struct { + struct + { uint low; uint high; } @@ -121,6 +114,7 @@ private template CustomFloatParams(uint bits) private template CustomFloatParams(uint precision, uint exponentWidth, CustomFloatFlags flags) { + import std.typetuple : TypeTuple; alias CustomFloatParams = TypeTuple!( precision, @@ -136,234 +130,276 @@ private template CustomFloatParams(uint precision, uint exponentWidth, CustomFlo * for storage only; all operations on them are performed by first implicitly * extracting them to $(D real) first. After the operation is completed the * result can be stored in a custom floating-point value via assignment. - * - * Example: - * ---- - * // Define a 16-bit floating point values - * CustomFloat!16 x; // Using the number of bits - * CustomFloat!(10, 5) y; // Using the precision and exponent width - * CustomFloat!(10, 5,CustomFloatFlags.ieee) z; // Using the precision, exponent width and format flags - * CustomFloat!(10, 5,CustomFloatFlags.ieee, 15) w; // Using the precision, exponent width, format flags and exponent offset bias - * - * // Use the 16-bit floats mostly like normal numbers - * w = x*y - 1; - * writeln(w); - * - * // Functions calls require conversion - * z = sin(+x) + cos(+y); // Use unary plus to concisely convert to a real - * z = sin(x.re) + cos(y.re); // Or use the .re property to convert to a real - * z = sin(x.get!float) + cos(y.get!float); // Or use get!T - * z = sin(cast(float)x) + cos(cast(float)y); // Or use cast(T) to explicitly convert - * - * // Define a 8-bit custom float for storing probabilities - * alias Probability = CustomFloat!(4, 4, CustomFloatFlags.ieee^CustomFloatFlags.probability^CustomFloatFlags.signed ); - * auto p = Probability(0.5); - * ---- */ template CustomFloat(uint bits) -if (bits == 8 || bits == 16 || bits == 32 || bits == 64 || bits == 80) + if (bits == 8 || bits == 16 || bits == 32 || bits == 64 || bits == 80) { alias CustomFloat = CustomFloat!(CustomFloatParams!(bits)); } + /// ditto template CustomFloat(uint precision, uint exponentWidth, CustomFloatFlags flags = CustomFloatFlags.ieee) -if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && precision + exponentWidth > 0) + if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && precision + exponentWidth > 0) { alias CustomFloat = CustomFloat!(CustomFloatParams!(precision, exponentWidth, flags)); } + +/// +unittest +{ + // Define a 16-bit floating point values + CustomFloat!16 x; // Using the number of bits + CustomFloat!(10, 5) y; // Using the precision and exponent width + CustomFloat!(10, 5,CustomFloatFlags.ieee) z; // Using the precision, exponent width and format flags + CustomFloat!(10, 5,CustomFloatFlags.ieee, 15) w; // Using the precision, exponent width, format flags and exponent offset bias + + // Use the 16-bit floats mostly like normal numbers + w = x*y - 1; + + // Functions calls require conversion + z = sin(+x) + cos(+y); // Use unary plus to concisely convert to a real + z = sin(x.get!float) + cos(y.get!float); // Or use get!T + z = sin(cast(float)x) + cos(cast(float)y); // Or use cast(T) to explicitly convert + + // Define a 8-bit custom float for storing probabilities + alias Probability = CustomFloat!(4, 4, CustomFloatFlags.ieee^CustomFloatFlags.probability^CustomFloatFlags.signed ); + auto p = Probability(0.5); +} + /// ditto -struct CustomFloat( - uint precision, // fraction bits (23 for float) - uint exponentWidth, // exponent bits (8 for float) Exponent width - CustomFloatFlags flags, - uint bias) - if(( (flags & flags.signed) + precision + exponentWidth) % 8 == 0 && - precision + exponentWidth > 0) +struct CustomFloat(uint precision, // fraction bits (23 for float) + uint exponentWidth, // exponent bits (8 for float) Exponent width + CustomFloatFlags flags, + uint bias) + if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && + precision + exponentWidth > 0) { - private: - // get the correct unsigned bitfield type to support > 32 bits - template uType(uint bits) { - static if(bits <= size_t.sizeof*8) alias uType = size_t; - else alias uType = ulong ; - } + import std.bitmanip; + import std.typetuple; +private: + // get the correct unsigned bitfield type to support > 32 bits + template uType(uint bits) + { + static if(bits <= size_t.sizeof*8) alias uType = size_t; + else alias uType = ulong ; + } - // get the correct signed bitfield type to support > 32 bits - template sType(uint bits) { - static if(bits <= ptrdiff_t.sizeof*8-1) alias sType = ptrdiff_t; - else alias sType = long; - } + // get the correct signed bitfield type to support > 32 bits + template sType(uint bits) + { + static if(bits <= ptrdiff_t.sizeof*8-1) alias sType = ptrdiff_t; + else alias sType = long; + } - alias T_sig = uType!precision; - alias T_exp = uType!exponentWidth; - alias T_signed_exp = sType!exponentWidth; + alias T_sig = uType!precision; + alias T_exp = uType!exponentWidth; + alias T_signed_exp = sType!exponentWidth; - alias Flags = CustomFloatFlags; + alias Flags = CustomFloatFlags; - // Facilitate converting numeric types to custom float - union ToBinary(F) + // Facilitate converting numeric types to custom float + union ToBinary(F) if (is(typeof(CustomFloatParams!(F.sizeof*8))) || is(F == real)) - { - F set; + { + F set; - // If on Linux or Mac, where 80-bit reals are padded, ignore the - // padding. - CustomFloat!(CustomFloatParams!(min(F.sizeof*8, 80))) get; + // If on Linux or Mac, where 80-bit reals are padded, ignore the + // padding. + import std.algorithm : min; + CustomFloat!(CustomFloatParams!(min(F.sizeof*8, 80))) get; - // Convert F to the correct binary type. - static typeof(get) opCall(F value) { - ToBinary r; - r.set = value; - return r.get; - } - alias get this; - } - - // Perform IEEE rounding with round to nearest detection - void roundedShift(T,U)(ref T sig, U shift) { - if( sig << (T.sizeof*8 - shift) == cast(T) 1uL << (T.sizeof*8 - 1) ) { - // round to even - sig >>= shift; - sig += sig & 1; - } else { - sig >>= shift - 1; - sig += sig & 1; - // Perform standard rounding - sig >>= 1; - } + // Convert F to the correct binary type. + static typeof(get) opCall(F value) + { + ToBinary r; + r.set = value; + return r.get; } + alias get this; + } - // Convert the current value to signed exponent, normalized form - void toNormalized(T,U)(ref T sig, ref U exp) { - sig = significand; - auto shift = (T.sizeof*8) - precision; - exp = exponent; - static if(flags&(Flags.infinity|Flags.nan)) { - // Handle inf or nan - if(exp == exponent_max) { - exp = exp.max; - sig <<= shift; - static if(flags&Flags.storeNormalized) { - // Save inf/nan in denormalized format - sig >>= 1; - sig += cast(T) 1uL << (T.sizeof*8 - 1); - } - return; + // Perform IEEE rounding with round to nearest detection + void roundedShift(T,U)(ref T sig, U shift) + { + if (sig << (T.sizeof*8 - shift) == cast(T) 1uL << (T.sizeof*8 - 1)) + { + // round to even + sig >>= shift; + sig += sig & 1; + } + else + { + sig >>= shift - 1; + sig += sig & 1; + // Perform standard rounding + sig >>= 1; + } + } + + // Convert the current value to signed exponent, normalized form + void toNormalized(T,U)(ref T sig, ref U exp) + { + sig = significand; + auto shift = (T.sizeof*8) - precision; + exp = exponent; + static if (flags&(Flags.infinity|Flags.nan)) + { + // Handle inf or nan + if (exp == exponent_max) + { + exp = exp.max; + sig <<= shift; + static if (flags&Flags.storeNormalized) + { + // Save inf/nan in denormalized format + sig >>= 1; + sig += cast(T) 1uL << (T.sizeof*8 - 1); } + return; } - if( (~flags&Flags.storeNormalized) || - // Convert denormalized form to normalized form - ((flags&Flags.allowDenorm)&&(exp==0)) ){ - if(sig > 0) { - auto shift2 = precision - bsr64(sig); - exp -= shift2-1; - shift += shift2; - } else { // value = 0.0 - exp = exp.min; - return; - } + } + if ((~flags&Flags.storeNormalized) || + // Convert denormalized form to normalized form + ((flags&Flags.allowDenorm) && exp==0)) + { + if(sig > 0) + { + auto shift2 = precision - bsr64(sig); + exp -= shift2-1; + shift += shift2; } - sig <<= shift; - exp -= bias; - } - - // Set the current value from signed exponent, normalized form - void fromNormalized(T,U)(ref T sig, ref U exp) { - auto shift = (T.sizeof*8) - precision; - if(exp == exp.max) { - // infinity or nan - exp = exponent_max; - static if(flags & Flags.storeNormalized) sig <<= 1; - // convert back to normalized form - static if(~flags & Flags.infinity) - // No infinity support? - enforce(sig != 0,"Infinity floating point value assigned to a " - ~ typeof(this).stringof~" (no infinity support)."); - static if(~flags & Flags.nan) // No NaN support? - enforce(sig == 0,"NaN floating point value assigned to a " ~ - typeof(this).stringof~" (no nan support)."); - sig >>= shift; + else // value = 0.0 + { + exp = exp.min; return; } - if(exp == exp.min){ // 0.0 - exp = 0; - sig = 0; - return; - } + } + sig <<= shift; + exp -= bias; + } + + // Set the current value from signed exponent, normalized form + void fromNormalized(T,U)(ref T sig, ref U exp) + { + auto shift = (T.sizeof*8) - precision; + if (exp == exp.max) + { + // infinity or nan + exp = exponent_max; + static if (flags & Flags.storeNormalized) + sig <<= 1; + + // convert back to normalized form + static if (~flags & Flags.infinity) + // No infinity support? + enforce(sig != 0, "Infinity floating point value assigned to a " + ~ typeof(this).stringof~" (no infinity support)."); + + static if (~flags & Flags.nan) // No NaN support? + enforce(sig == 0,"NaN floating point value assigned to a " ~ + typeof(this).stringof~" (no nan support)."); + sig >>= shift; + return; + } + if (exp == exp.min) // 0.0 + { + exp = 0; + sig = 0; + return; + } - exp += bias; - if( exp <= 0 ) { - static if( ( flags&Flags.allowDenorm) || - // Convert from normalized form to denormalized - (~flags&Flags.storeNormalized) ) { - shift += -exp; - roundedShift(sig,1); - sig += cast(T) 1uL << (T.sizeof*8 - 1); - // Add the leading 1 - exp = 0; - } else enforce( (flags&Flags.storeNormalized) && exp == 0, + exp += bias; + if (exp <= 0) + { + static if ((flags&Flags.allowDenorm) || + // Convert from normalized form to denormalized + (~flags&Flags.storeNormalized)) + { + shift += -exp; + roundedShift(sig,1); + sig += cast(T) 1uL << (T.sizeof*8 - 1); + // Add the leading 1 + exp = 0; + } + else + enforce((flags&Flags.storeNormalized) && exp == 0, "Underflow occured assigning to a " ~ typeof(this).stringof ~ " (no denormal support)."); - } else { - static if(~flags&Flags.storeNormalized) { - // Convert from normalized form to denormalized - roundedShift(sig,1); - sig += cast(T) 1uL << (T.sizeof*8 - 1); - // Add the leading 1 - } + } + else + { + static if (~flags&Flags.storeNormalized) + { + // Convert from normalized form to denormalized + roundedShift(sig,1); + sig += cast(T) 1uL << (T.sizeof*8 - 1); + // Add the leading 1 } + } - if(shift > 0) - roundedShift(sig,shift); - if(sig > significand_max) { - // handle significand overflow (should only be 1 bit) - static if(~flags&Flags.storeNormalized) { - sig >>= 1; - } else - sig &= significand_max; - exp++; + if (shift > 0) + roundedShift(sig,shift); + if (sig > significand_max) + { + // handle significand overflow (should only be 1 bit) + static if (~flags&Flags.storeNormalized) + { + sig >>= 1; } - static if((flags&Flags.allowDenormZeroOnly)==Flags.allowDenormZeroOnly) { - // disallow non-zero denormals - if(exp == 0) { - sig <<= 1; - if(sig > significand_max && (sig&significand_max) > 0 ) - // Check and round to even - exp++; - sig = 0; - } + else + sig &= significand_max; + exp++; + } + static if ((flags&Flags.allowDenormZeroOnly)==Flags.allowDenormZeroOnly) + { + // disallow non-zero denormals + if (exp == 0) + { + sig <<= 1; + if (sig > significand_max && (sig&significand_max) > 0) + // Check and round to even + exp++; + sig = 0; } + } - if(exp >= exponent_max ) { - static if( flags&(Flags.infinity|Flags.nan) ) { - sig = 0; - exp = exponent_max; - static if(~flags&(Flags.infinity)) - enforce( false, "Overflow occured assigning to a " ~ - typeof(this).stringof~" (no infinity support)."); - } else - enforce( exp == exponent_max, "Overflow occured assigning to a " - ~ typeof(this).stringof~" (no infinity support)."); + if (exp >= exponent_max) + { + static if (flags&(Flags.infinity|Flags.nan)) + { + sig = 0; + exp = exponent_max; + static if (~flags&(Flags.infinity)) + enforce(false, "Overflow occured assigning to a " ~ + typeof(this).stringof~" (no infinity support)."); } + else + enforce(exp == exponent_max, "Overflow occured assigning to a " + ~ typeof(this).stringof~" (no infinity support)."); } + } - public: - static if( precision == 64 ) { // CustomFloat!80 support hack - ulong significand; - enum ulong significand_max = ulong.max; - mixin(bitfields!( - T_exp , "exponent", exponentWidth, - bool , "sign" , flags & flags.signed )); - - } else { - mixin(bitfields!( - T_sig, "significand", precision, - T_exp, "exponent" , exponentWidth, - bool , "sign" , flags & flags.signed )); - } +public: + static if (precision == 64) // CustomFloat!80 support hack + { + ulong significand; + enum ulong significand_max = ulong.max; + mixin(bitfields!( + T_exp , "exponent", exponentWidth, + bool , "sign" , flags & flags.signed )); + } + else + { + mixin(bitfields!( + T_sig, "significand", precision, + T_exp, "exponent" , exponentWidth, + bool , "sign" , flags & flags.signed )); + } /// Returns: infinity value static if (flags & Flags.infinity) - static @property CustomFloat infinity() { + static @property CustomFloat infinity() + { CustomFloat value; static if (flags & Flags.signed) value.sign = 0; @@ -374,7 +410,8 @@ struct CustomFloat( /// Returns: NaN value static if (flags & Flags.nan) - static @property CustomFloat nan() { + static @property CustomFloat nan() + { CustomFloat value; static if (flags & Flags.signed) value.sign = 0; @@ -384,29 +421,34 @@ struct CustomFloat( } /// Returns: number of decimal digits of precision - static @property size_t dig(){ + static @property size_t dig() + { auto shiftcnt = precision - ((flags&Flags.storeNormalized) != 0); auto x = (shiftcnt == 64) ? 0 : 1uL << shiftcnt; return cast(size_t) log10(x); } /// Returns: smallest increment to the value 1 - static @property CustomFloat epsilon() { - CustomFloat value; - static if (flags & Flags.signed) - value.sign = 0; - T_signed_exp exp = -precision; - T_sig sig = 0; - value.fromNormalized(sig,exp); - if(exp == 0 && sig == 0) { // underflowed to zero - static if((flags&Flags.allowDenorm) || (~flags&Flags.storeNormalized)) - sig = 1; - else - sig = cast(T) 1uL << (precision - 1); - } - value.exponent = cast(value.T_exp) exp; - value.significand = cast(value.T_sig) sig; - return value; + static @property CustomFloat epsilon() + { + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + T_signed_exp exp = -precision; + T_sig sig = 0; + + value.fromNormalized(sig,exp); + if (exp == 0 && sig == 0) // underflowed to zero + { + static if ((flags&Flags.allowDenorm) || + (~flags&Flags.storeNormalized)) + sig = 1; + else + sig = cast(T) 1uL << (precision - 1); + } + value.exponent = cast(value.T_exp) exp; + value.significand = cast(value.T_sig) sig; + return value; } /// the number of bits in mantissa @@ -425,39 +467,44 @@ struct CustomFloat( enum min_exp = cast(T_signed_exp)-bias +1+ ((flags&Flags.allowDenorm)!=0); /// Returns: largest representable value that's not infinity - static @property CustomFloat max() { - CustomFloat value; - static if (flags & Flags.signed) - value.sign = 0; - value.exponent = exponent_max - ((flags&(flags.infinity|flags.nan)) != 0); - value.significand = significand_max; - return value; + static @property CustomFloat max() + { + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + value.exponent = exponent_max - ((flags&(flags.infinity|flags.nan)) != 0); + value.significand = significand_max; + return value; } /// Returns: smallest representable normalized value that's not 0 static @property CustomFloat min_normal() { - CustomFloat value; - static if (flags & Flags.signed) - value.sign = 0; - value.exponent = 1; - static if(flags&Flags.storeNormalized) - value.significand = 0; - else - value.significand = cast(T_sig) 1uL << (precision - 1); - return value; + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + value.exponent = 1; + static if(flags&Flags.storeNormalized) + value.significand = 0; + else + value.significand = cast(T_sig) 1uL << (precision - 1); + return value; } /// Returns: real part - @property CustomFloat re() { return this; } + @property CustomFloat re() { return this; } /// Returns: imaginary part - static @property CustomFloat im() { return CustomFloat(0.0f); } + static @property CustomFloat im() { return CustomFloat(0.0f); } /// Initialize from any $(D real) compatible type. - this(F)(F input) if (__traits(compiles, cast(real)input )) { this = input; } + this(F)(F input) if (__traits(compiles, cast(real)input )) + { + this = input; + } /// Self assignment - void opAssign(F:CustomFloat)(F input) { + void opAssign(F:CustomFloat)(F input) + { static if (flags & Flags.signed) sign = input.sign; exponent = input.exponent; @@ -466,12 +513,14 @@ struct CustomFloat( /// Assigns from any $(D real) compatible type. void opAssign(F)(F input) - if (__traits(compiles, cast(real)input )) + if (__traits(compiles, cast(real)input)) { + import std.conv: text; + static if (staticIndexOf!(Unqual!F, float, double, real) >= 0) + auto value = ToBinary!(Unqual!F)(input); + else + auto value = ToBinary!(real )(input); - static if( staticIndexOf!(Unqual!F, float, double, real) >= 0 ) - auto value = ToBinary!(Unqual!F)(input); - else auto value = ToBinary!(real )(input); // Assign the sign bit static if (~flags & Flags.signed) enforce( (!value.sign)^((flags&flags.negativeUnsigned)>0) , @@ -480,18 +529,17 @@ struct CustomFloat( else sign = value.sign; - CommonType!(T_signed_exp ,value.T_signed_exp ) exp = value.exponent; - CommonType!(T_sig, value.T_sig ) sig = value.significand; + CommonType!(T_signed_exp ,value.T_signed_exp) exp = value.exponent; + CommonType!(T_sig, value.T_sig ) sig = value.significand; value.toNormalized(sig,exp); fromNormalized(sig,exp); - assert(exp <= exponent_max, text(typeof(this).stringof ~ - " exponent too large: " ,exp," > ",exponent_max, "\t",input,"\t",sig) ); + " exponent too large: " ,exp," > ",exponent_max, "\t",input,"\t",sig)); assert(sig <= significand_max, text(typeof(this).stringof ~ " significand too large: ",sig," > ",significand_max, - "\t",input,"\t",exp," ",exponent_max) ); + "\t",input,"\t",exp," ",exponent_max)); exponent = cast(T_exp) exp; significand = cast(T_sig) sig; } @@ -500,11 +548,16 @@ struct CustomFloat( @property F get(F)() if (staticIndexOf!(Unqual!F, float, double, real) >= 0) { + import std.conv: text; + ToBinary!F result; - static if (flags&Flags.signed) result.sign = sign; - else result.sign = (flags&flags.negativeUnsigned) > 0; - CommonType!(T_signed_exp ,result.get.T_signed_exp ) exp = exponent; // Assign the exponent and fraction + static if (flags&Flags.signed) + result.sign = sign; + else + result.sign = (flags&flags.negativeUnsigned) > 0; + + CommonType!(T_signed_exp ,result.get.T_signed_exp ) exp = exponent; // Assign the exponent and fraction CommonType!(T_sig, result.get.T_sig ) sig = significand; toNormalized(sig,exp); @@ -515,48 +568,70 @@ struct CustomFloat( result.significand = cast(result.get.T_sig) sig; return result.set; } + ///ditto T opCast(T)() if (__traits(compiles, get!T )) { return get!T; } /// Convert the CustomFloat to a real and perform the relavent operator on the result - real opUnary(string op)() if( __traits(compiles, mixin(op~`(get!real)`)) || op=="++" || op=="--" ){ - static if(op=="++" || op=="--") { + real opUnary(string op)() + if (__traits(compiles, mixin(op~`(get!real)`)) || op=="++" || op=="--") + { + static if (op=="++" || op=="--") + { auto result = get!real; this = mixin(op~`result`); return result; - } else + } + else return mixin(op~`get!real`); } /// ditto - real opBinary(string op,T)(T b) if( __traits(compiles, mixin(`get!real`~op~`b`) ) ) { + real opBinary(string op,T)(T b) + if (__traits(compiles, mixin(`get!real`~op~`b`))) + { return mixin(`get!real`~op~`b`); } /// ditto - real opBinaryRight(string op,T)(T a) if( __traits(compiles, mixin(`a`~op~`get!real`) ) && - !__traits(compiles, mixin(`get!real`~op~`b`) ) ) { + real opBinaryRight(string op,T)(T a) + if ( __traits(compiles, mixin(`a`~op~`get!real`)) && + !__traits(compiles, mixin(`get!real`~op~`b`))) + { return mixin(`a`~op~`get!real`); } /// ditto - int opCmp(T)(auto ref T b) if(__traits(compiles, cast(real)b ) ) { + int opCmp(T)(auto ref T b) + if (__traits(compiles, cast(real)b)) + { auto x = get!real; auto y = cast(real) b; return (x>=y)-(x<=y); } /// ditto - void opOpAssign(string op, T)(auto ref T b) if ( __traits(compiles, mixin(`get!real`~op~`cast(real)b`))) { + void opOpAssign(string op, T)(auto ref T b) + if (__traits(compiles, mixin(`get!real`~op~`cast(real)b`))) + { return mixin(`this = this `~op~` cast(real)b`); } /// ditto - string toString() { return to!string(get!real); } + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + sink.formatValue(get!real, fmt); + } + } } unittest { + import std.typetuple; alias FPTypes = TypeTuple!( CustomFloat!(5, 10), @@ -588,7 +663,13 @@ unittest assert(x.get!float == 1 / 16.0F); assert(x.get!double == 1 / 16.0); } +} +unittest +{ + import std.conv; + CustomFloat!(5, 10) y = CustomFloat!(5, 10)(0.125); + assert(y.to!string == "0.125"); } /** @@ -598,18 +679,6 @@ calculation intended to ultimately yield a result of type $(D F) real)). When doing a multi-step computation, you may want to store intermediate results as $(D FPTemporary!F). -Example: ----- -// Average numbers in an array -double avg(in double[] a) -{ - if (a.length == 0) return 0; - FPTemporary!double result = 0; - foreach (e; a) result += e; - return result / a.length; -} ----- - The necessity of $(D FPTemporary) stems from the optimized floating-point operations and registers present in virtually all processors. When adding numbers in the example above, the addition may @@ -626,34 +695,45 @@ Finally, there is no guarantee that using $(D FPTemporary!F) will always be fastest, as the speed of floating-point calculations depends on very many factors. */ -template FPTemporary(F) if (isFloatingPoint!F) +template FPTemporary(F) + if (isFloatingPoint!F) { alias FPTemporary = real; } +/// +unittest +{ + // Average numbers in an array + double avg(in double[] a) + { + if (a.length == 0) return 0; + FPTemporary!double result = 0; + foreach (e; a) result += e; + return result / a.length; + } + + auto a = [1.0, 2.0, 3.0]; + assert(approxEqual(avg(a), 2)); +} + /** Implements the $(WEB tinyurl.com/2zb9yr, secant method) for finding a root of the function $(D fun) starting from points $(D [xn_1, x_n]) (ideally close to the root). $(D Num) may be $(D float), $(D double), or $(D real). - -Example: - ----- -float f(float x) { - return cos(x) - x*x*x; -} -auto x = secantMethod!(f)(0f, 1f); -assert(approxEqual(x, 0.865474)); ----- */ template secantMethod(alias fun) { - Num secantMethod(Num)(Num xn_1, Num xn) { + import std.functional : unaryFun; + Num secantMethod(Num)(Num xn_1, Num xn) + { auto fxn = unaryFun!(fun)(xn_1), d = xn_1 - xn; typeof(fxn) fxn_1; + xn = xn_1; - while (!approxEqual(d, 0) && isfinite(d)) { + while (!approxEqual(d, 0) && isFinite(d)) + { xn_1 = xn; xn -= d; fxn_1 = fxn; @@ -664,10 +744,22 @@ template secantMethod(alias fun) } } +/// +unittest +{ + float f(float x) + { + return cos(x) - x*x*x; + } + auto x = secantMethod!(f)(0f, 1f); + assert(approxEqual(x, 0.865474)); +} + unittest { scope(failure) stderr.writeln("Failure testing secantMethod"); - float f(float x) { + float f(float x) + { return cos(x) - x*x*x; } immutable x = secantMethod!(f)(0f, 1f); @@ -686,15 +778,6 @@ private bool oppositeSigns(T1, T2)(T1 a, T2 b) return signbit(a) != signbit(b); } -//regression control -unittest -{ - static assert(__traits(compiles, findRoot((float x)=>cast(real)x, float.init, float.init))); - static assert(__traits(compiles, findRoot!real((x)=>cast(double)x, real.init, real.init))); -} - - - public: /** Find a real root of a real function f(x) via bracketing. @@ -721,14 +804,25 @@ public: * www.netlib.org,www.netlib.org) as algorithm TOMS478. * */ -T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, - scope bool delegate(T lo, T hi) tolerance = (T a, T b) => false) +T findRoot(T, DF, DT)(scope DF f, in T a, in T b, + scope DT tolerance) //= (T a, T b) => false) + if( + isFloatingPoint!T && + is(typeof(tolerance(T.init, T.init)) : bool) && + is(typeof(f(T.init)) == R, R) && isFloatingPoint!R + ) { auto r = findRoot(f, a, b, f(a), f(b), tolerance); // Return the first value if it is smaller or NaN return !(fabs(r[2]) > fabs(r[3])) ? r[0] : r[1]; } +///ditto +T findRoot(T, DF)(scope DF f, in T a, in T b) +{ + return findRoot(f, a, b, (T a, T b) => false); +} + /** Find root of a real function f(x) by bracketing, allowing the * termination condition to be specified. * @@ -762,25 +856,36 @@ T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, * root was found, both of the first two elements will contain the * root, and the second pair of elements will be 0. */ -Tuple!(T, T, R, R) findRoot(T,R)(scope R delegate(T) f, in T ax, in T bx, in R fax, in R fbx, - scope bool delegate(T lo, T hi) tolerance = (T a, T b) => false) -in { - assert(!ax.isNaN && !bx.isNaN, "Limits must not be NaN"); +Tuple!(T, T, R, R) findRoot(T, R, DF, DT)(scope DF f, in T ax, in T bx, in R fax, in R fbx, + scope DT tolerance) // = (T a, T b) => false) + if( + isFloatingPoint!T && + is(typeof(tolerance(T.init, T.init)) : bool) && + is(typeof(f(T.init)) == R) && isFloatingPoint!R + ) +in +{ + assert(!ax.isNaN() && !bx.isNaN(), "Limits must not be NaN"); assert(signbit(fax) != signbit(fbx), "Parameters must bracket the root."); } -body { -// Author: Don Clugston. This code is (heavily) modified from TOMS748 (www.netlib.org). -// The changes to improve the worst-cast performance are entirely original. +body +{ + // Author: Don Clugston. This code is (heavily) modified from TOMS748 + // (www.netlib.org). The changes to improve the worst-cast performance are + // entirely original. T a, b, d; // [a..b] is our current bracket. d is the third best guess. R fa, fb, fd; // Values of f at a, b, d. bool done = false; // Has a root been found? // Allow ax and bx to be provided in reverse order - if (ax <= bx) { + if (ax <= bx) + { a = ax; fa = fax; b = bx; fb = fbx; - } else { + } + else + { a = bx; fa = fbx; b = ax; fb = fax; } @@ -788,8 +893,9 @@ body { // Test the function at point c; update brackets accordingly void bracket(T c) { - T fc = f(c); - if (fc == 0 || fc.isNaN) { // Exact solution, or NaN + R fc = f(c); + if (fc == 0 || fc.isNaN()) // Exact solution, or NaN + { a = c; fa = fc; d = c; @@ -797,13 +903,17 @@ body { done = true; return; } + // Determine new enclosing interval - if (signbit(fa) != signbit(fc)) { + if (signbit(fa) != signbit(fc)) + { d = b; fd = fb; b = c; fb = fc; - } else { + } + else + { d = a; fd = fa; a = c; @@ -815,22 +925,29 @@ body { a and b differ so wildly in magnitude that the result would be meaningless, perform a bisection instead. */ - T secant_interpolate(T a, T b, T fa, T fb) + static T secant_interpolate(T a, T b, R fa, R fb) { - if (( ((a - b) == a) && b!=0) || (a!=0 && ((b - a) == b))) { + if (( ((a - b) == a) && b!=0) || (a!=0 && ((b - a) == b))) + { // Catastrophic cancellation - if (a == 0) a = copysign(0.0L, b); - else if (b == 0) b = copysign(0.0L, a); - else if (signbit(a) != signbit(b)) return 0; + if (a == 0) + a = copysign(T(0), b); + else if (b == 0) + b = copysign(T(0), a); + else if (signbit(a) != signbit(b)) + return 0; T c = ieeeMean(a, b); return c; } - // avoid overflow - if (b - a > T.max) return b / 2.0 + a / 2.0; - if (fb - fa > T.max) return a - (b - a) / 2; - T c = a - (fa / (fb - fa)) * (b - a); - if (c == a || c == b) return (a + b) / 2; - return c; + // avoid overflow + if (b - a > T.max) + return b / 2 + a / 2; + if (fb - fa > R.max) + return a - (b - a) / 2; + T c = a - (fa / (fb - fa)) * (b - a); + if (c == a || c == b) + return (a + b) / 2; + return c; } /* Uses 'numsteps' newton steps to approximate the zero in [a..b] of the @@ -841,47 +958,59 @@ body { T newtonQuadratic(int numsteps) { // Find the coefficients of the quadratic polynomial. - T a0 = fa; - T a1 = (fb - fa)/(b - a); - T a2 = ((fd - fb)/(d - b) - a1)/(d - a); + immutable T a0 = fa; + immutable T a1 = (fb - fa)/(b - a); + immutable T a2 = ((fd - fb)/(d - b) - a1)/(d - a); // Determine the starting point of newton steps. T c = oppositeSigns(a2, fa) ? a : b; // start the safeguarded newton steps. - for (int i = 0; i= b)) { + if (c.isNaN() || (c <= a) || (c >= b)) + { // DAC: If the interpolation predicts a or b, it's // probable that it's the actual root. Only allow this if // we're already close to the root. - if (c == a && a - b != a) { + if (c == a && a - b != a) + { c = nextUp(a); } - else if (c == b && a - b != -b) { + else if (c == b && a - b != -b) + { c = nextDown(b); - } else { + } + else + { ok = false; } } } - if (!ok) { + if (!ok) + { // DAC: Alefeld doesn't explain why the number of newton steps // should vary. c = newtonQuadratic(distinct ? 3 : 2); - if(c.isNaN || (c <= a) || (c >= b)) { + if (c.isNaN() || (c <= a) || (c >= b)) + { // Failure, try a secant step: c = secant_interpolate(a, b, fa, fb); } @@ -930,40 +1067,54 @@ whileloop: e = d; fe = fd; bracket(c); - if( done || ( b == nextUp(a)) || tolerance(a, b)) + if (done || ( b == nextUp(a)) || tolerance(a, b)) break whileloop; if (itnum == 2) continue whileloop; } + // Now we take a double-length secant step: T u; R fu; - if(fabs(fa) < fabs(fb)) { + if (fabs(fa) < fabs(fb)) + { u = a; fu = fa; - } else { + } + else + { u = b; fu = fb; } c = u - 2 * (fu / (fb - fa)) * (b - a); + // DAC: If the secant predicts a value equal to an endpoint, it's // probably false. - if(c==a || c==b || c.isNaN || fabs(c - u) > (b - a) / 2) { - if ((a-b) == a || (b-a) == b) { - if ( (a>0 && b<0) || (a<0 && b>0) ) c = 0; - else { - if (a==0) c = ieeeMean(cast(T)copysign(0.0L, b), b); - else if (b==0) c = ieeeMean(cast(T)copysign(0.0L, a), a); - else c = ieeeMean(a, b); + if (c==a || c==b || c.isNaN() || fabs(c - u) > (b - a) / 2) + { + if ((a-b) == a || (b-a) == b) + { + if ((a>0 && b<0) || (a<0 && b>0)) + c = 0; + else + { + if (a==0) + c = ieeeMean(copysign(T(0), b), b); + else if (b==0) + c = ieeeMean(copysign(T(0), a), a); + else + c = ieeeMean(a, b); } - } else { + } + else + { c = a + (b - a) / 2; } } e = d; fe = fd; bracket(c); - if(done || (b == nextUp(a)) || tolerance(a, b)) + if (done || (b == nextUp(a)) || tolerance(a, b)) break; // IMPROVE THE WORST-CASE PERFORMANCE @@ -972,28 +1123,36 @@ whileloop: // yet, or if we don't yet know what the exponent is, // perform a binary chop. - if( (a==0 || b==0 || - (fabs(a) >= 0.5 * fabs(b) && fabs(b) >= 0.5 * fabs(a))) - && (b - a) < 0.25 * (b0 - a0)) { - baditer = 1; - continue; - } + if ((a==0 || b==0 || + (fabs(a) >= T(0.5) * fabs(b) && fabs(b) >= T(0.5) * fabs(a))) + && (b - a) < T(0.25) * (b0 - a0)) + { + baditer = 1; + continue; + } + // DAC: If this happens on consecutive iterations, we probably have a // pathological function. Perform a number of bisections equal to the // total number of consecutive bad iterations. - if ((b - a) < 0.25 * (b0 - a0)) baditer = 1; - for (int QQ = 0; QQ < baditer ;++QQ) { + if ((b - a) < T(0.25) * (b0 - a0)) + baditer = 1; + foreach (int QQ; 0..baditer) + { e = d; fe = fd; T w; - if ((a>0 && b<0) ||(a<0 && b>0)) w = 0; - else { + if ((a>0 && b<0) || (a<0 && b>0)) + w = 0; + else + { T usea = a; T useb = b; - if (a == 0) usea = copysign(0.0L, b); - else if (b == 0) useb = copysign(0.0L, a); + if (a == 0) + usea = copysign(T(0), b); + else if (b == 0) + useb = copysign(T(0), a); w = ieeeMean(usea, useb); } bracket(w); @@ -1003,33 +1162,51 @@ whileloop: return Tuple!(T, T, R, R)(a, b, fa, fb); } -unittest +///ditto +Tuple!(T, T, R, R) findRoot(T, R, DF, DT)(scope DF f, in T ax, in T bx, in R fax, in R fbx) +{ + return findRoot(f, ax, bx, fax, fbx, (T a, T b) => false); +} + +///ditto +T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, + scope bool delegate(T lo, T hi) tolerance = (T a, T b) => false) +{ + return findRoot!(T, R delegate(T), bool delegate(T lo, T hi))(f, a, b, tolerance); +} + +nothrow unittest { int numProblems = 0; int numCalls; - void testFindRoot(real delegate(real) f, real x1, real x2) { - numCalls=0; - ++numProblems; - assert(!x1.isNaN && !x2.isNaN); + void testFindRoot(real delegate(real) @nogc @safe nothrow pure f , real x1, real x2) @nogc @safe nothrow pure + { + //numCalls=0; + //++numProblems; + assert(!x1.isNaN() && !x2.isNaN()); assert(signbit(x1) != signbit(x2)); auto result = findRoot(f, x1, x2, f(x1), f(x2), (real lo, real hi) { return false; }); auto flo = f(result[0]); auto fhi = f(result[1]); - if (flo!=0) { + if (flo!=0) + { assert(oppositeSigns(flo, fhi)); } } // Test functions - real cubicfn (real x) { - ++numCalls; - if (x>float.max) x = float.max; - if (x<-double.max) x = -double.max; - // This has a single real root at -59.286543284815 - return 0.386*x*x*x + 23*x*x + 15.7*x + 525.2; + real cubicfn(real x) @nogc @safe nothrow pure + { + //++numCalls; + if (x>float.max) + x = float.max; + if (x<-double.max) + x = -double.max; + // This has a single real root at -59.286543284815 + return 0.386*x*x*x + 23*x*x + 15.7*x + 525.2; } // Test a function with more than one root. real multisine(real x) { ++numCalls; return sin(x); } @@ -1048,7 +1225,8 @@ unittest int powercalls = 0; - real power(real x) { + real power(real x) + { ++powercalls; ++numCalls; return pow(x, n) + double.min_normal; @@ -1064,7 +1242,8 @@ unittest // I get: 231 (0.48/bit). // IE this is 10X faster in Alefeld's worst case numProblems=0; - foreach(k; power_nvals) { + foreach (k; power_nvals) + { n = k; //testFindRoot(&power, -1, 10); } @@ -1074,7 +1253,8 @@ unittest // Tests from Alefeld paper int [9] alefeldSums; - real alefeld0(real x){ + real alefeld0(real x) + { ++alefeldSums[0]; ++numCalls; real q = sin(x) - x/2; @@ -1082,61 +1262,68 @@ unittest q+=(2*i-5.0)*(2*i-5.0)/((x-i*i)*(x-i*i)*(x-i*i)); return q; } - real alefeld1(real x) { + real alefeld1(real x) + { ++numCalls; - ++alefeldSums[1]; - return ale_a*x + exp(ale_b * x); - } - real alefeld2(real x) { + ++alefeldSums[1]; + return ale_a*x + exp(ale_b * x); + } + real alefeld2(real x) + { ++numCalls; - ++alefeldSums[2]; - return pow(x, n) - ale_a; - } - real alefeld3(real x) { + ++alefeldSums[2]; + return pow(x, n) - ale_a; + } + real alefeld3(real x) + { ++numCalls; - ++alefeldSums[3]; - return (1.0 +pow(1.0L-n, 2))*x - pow(1.0L-n*x, 2); - } - real alefeld4(real x) { + ++alefeldSums[3]; + return (1.0 +pow(1.0L-n, 2))*x - pow(1.0L-n*x, 2); + } + real alefeld4(real x) + { ++numCalls; - ++alefeldSums[4]; - return x*x - pow(1-x, n); - } - - real alefeld5(real x) { + ++alefeldSums[4]; + return x*x - pow(1-x, n); + } + real alefeld5(real x) + { ++numCalls; - ++alefeldSums[5]; - return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4); - } - - real alefeld6(real x) { + ++alefeldSums[5]; + return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4); + } + real alefeld6(real x) + { + ++numCalls; + ++alefeldSums[6]; + return exp(-n*x)*(x-1.01L) + pow(x, n); + } + real alefeld7(real x) + { ++numCalls; - ++alefeldSums[6]; - return exp(-n*x)*(x-1.01L) + pow(x, n); - } + ++alefeldSums[7]; + return (n*x-1)/((n-1)*x); + } - real alefeld7(real x) { - ++numCalls; - ++alefeldSums[7]; - return (n*x-1)/((n-1)*x); - } - numProblems=0; - //testFindRoot(&alefeld0, PI_2, PI); - for (n=1; n<=10; ++n) { - //testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); - } - ale_a = -40; ale_b = -1; - //testFindRoot(&alefeld1, -9, 31); - ale_a = -100; ale_b = -2; - //testFindRoot(&alefeld1, -9, 31); - ale_a = -200; ale_b = -3; - //testFindRoot(&alefeld1, -9, 31); - int [] nvals_3 = [1, 2, 5, 10, 15, 20]; - int [] nvals_5 = [1, 2, 4, 5, 8, 15, 20]; - int [] nvals_6 = [1, 5, 10, 15, 20]; - int [] nvals_7 = [2, 5, 15, 20]; - - for(int i=4; i<12; i+=2) { + numProblems=0; + //testFindRoot(&alefeld0, PI_2, PI); + for (n=1; n<=10; ++n) + { + //testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); + } + ale_a = -40; ale_b = -1; + //testFindRoot(&alefeld1, -9, 31); + ale_a = -100; ale_b = -2; + //testFindRoot(&alefeld1, -9, 31); + ale_a = -200; ale_b = -3; + //testFindRoot(&alefeld1, -9, 31); + int [] nvals_3 = [1, 2, 5, 10, 15, 20]; + int [] nvals_5 = [1, 2, 4, 5, 8, 15, 20]; + int [] nvals_6 = [1, 5, 10, 15, 20]; + int [] nvals_7 = [2, 5, 15, 20]; + + for (int i=4; i<12; i+=2) + { n = i; ale_a = 0.2; //testFindRoot(&alefeld2, 0, 5); @@ -1144,27 +1331,34 @@ unittest //testFindRoot(&alefeld2, 0.95, 4.05); //testFindRoot(&alefeld2, 0, 1.5); } - foreach(i; nvals_3) { + foreach (i; nvals_3) + { n=i; //testFindRoot(&alefeld3, 0, 1); } - foreach(i; nvals_3) { + foreach (i; nvals_3) + { n=i; //testFindRoot(&alefeld4, 0, 1); } - foreach(i; nvals_5) { + foreach (i; nvals_5) + { n=i; //testFindRoot(&alefeld5, 0, 1); } - foreach(i; nvals_6) { + foreach (i; nvals_6) + { n=i; //testFindRoot(&alefeld6, 0, 1); } - foreach(i; nvals_7) { + foreach (i; nvals_7) + { n=i; //testFindRoot(&alefeld7, 0.01L, 1); } - real worstcase(real x) { ++numCalls; + real worstcase(real x) + { + ++numCalls; return x<0.3*real.max? -0.999e-3 : 1.0; } //testFindRoot(&worstcase, -real.max, real.max); @@ -1175,7 +1369,8 @@ unittest /* int grandtotal=0; - foreach(calls; alefeldSums) { + foreach (calls; alefeldSums) + { grandtotal+=calls; } grandtotal-=2*numProblems; @@ -1187,6 +1382,14 @@ unittest */ } +//regression control +unittest +{ + static assert(__traits(compiles, findRoot((float x)=>cast(real)x, float.init, float.init))); + static assert(__traits(compiles, findRoot!real((x)=>cast(double)x, real.init, real.init))); + static assert(__traits(compiles, findRoot((real x)=>cast(double)x, real.init, real.init))); +} + /** Computes $(LUCKY Euclidean distance) between input ranges $(D a) and $(D b). The two ranges must have the same length. The three-parameter @@ -1200,7 +1403,7 @@ euclideanDistance(Range1, Range2)(Range1 a, Range2 b) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) enforce(a.length == b.length); - typeof(return) result = 0; + Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { auto t = a.front - b.front; @@ -1218,7 +1421,7 @@ euclideanDistance(Range1, Range2, F)(Range1 a, Range2 b, F limit) limit *= limit; enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) enforce(a.length == b.length); - typeof(return) result = 0; + Unqual!(typeof(return)) result = 0; for (; ; a.popFront(), b.popFront()) { if (a.empty) @@ -1235,12 +1438,16 @@ euclideanDistance(Range1, Range2, F)(Range1 a, Range2 b, F limit) unittest { - double[] a = [ 1.0, 2.0, ]; - double[] b = [ 4.0, 6.0, ]; - assert(euclideanDistance(a, b) == 5); - assert(euclideanDistance(a, b, 5) == 5); - assert(euclideanDistance(a, b, 4) == 5); - assert(euclideanDistance(a, b, 2) == 3); + import std.typetuple; + foreach(T; TypeTuple!(double, const double, immutable double)) + { + T[] a = [ 1.0, 2.0, ]; + T[] b = [ 4.0, 6.0, ]; + assert(euclideanDistance(a, b) == 5); + assert(euclideanDistance(a, b, 5) == 5); + assert(euclideanDistance(a, b, 4) == 5); + assert(euclideanDistance(a, b, 2) == 3); + } } /** @@ -1252,11 +1459,11 @@ iteration. CommonType!(ElementType!(Range1), ElementType!(Range2)) dotProduct(Range1, Range2)(Range1 a, Range2 b) if (isInputRange!(Range1) && isInputRange!(Range2) && - !(isArray!(Range1) && isArray!(Range2))) + !(isArray!(Range1) && isArray!(Range2))) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) enforce(a.length == b.length); - typeof(return) result = 0; + Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { result += a.front * b.front; @@ -1266,13 +1473,13 @@ dotProduct(Range1, Range2)(Range1 a, Range2 b) } /// Ditto -Unqual!(CommonType!(F1, F2)) +CommonType!(F1, F2) dotProduct(F1, F2)(in F1[] avector, in F2[] bvector) { immutable n = avector.length; assert(n == bvector.length); auto avec = avector.ptr, bvec = bvector.ptr; - typeof(return) sum0 = 0, sum1 = 0; + Unqual!(typeof(return)) sum0 = 0, sum1 = 0; const all_endp = avec + n; const smallblock_endp = avec + (n & ~3); @@ -1298,7 +1505,8 @@ dotProduct(F1, F2)(in F1[] avector, in F2[] bvector) sum1 += avec[15] * bvec[15]; } - for (; avec != smallblock_endp; avec += 4, bvec += 4) { + for (; avec != smallblock_endp; avec += 4, bvec += 4) + { sum0 += avec[0] * bvec[0]; sum1 += avec[1] * bvec[1]; sum0 += avec[2] * bvec[2]; @@ -1320,10 +1528,14 @@ dotProduct(F1, F2)(in F1[] avector, in F2[] bvector) unittest { - double[] a = [ 1.0, 2.0, ]; - double[] b = [ 4.0, 6.0, ]; - assert(dotProduct(a, b) == 16); - assert(dotProduct([1, 3, -5], [4, -2, -1]) == 3); + import std.typetuple; + foreach(T; TypeTuple!(double, const double, immutable double)) + { + T[] a = [ 1.0, 2.0, ]; + T[] b = [ 4.0, 6.0, ]; + assert(dotProduct(a, b) == 16); + assert(dotProduct([1, 3, -5], [4, -2, -1]) == 3); + } // Make sure the unrolled loop codepath gets tested. static const x = @@ -1345,7 +1557,7 @@ cosineSimilarity(Range1, Range2)(Range1 a, Range2 b) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) enforce(a.length == b.length); - FPTemporary!(typeof(return)) norma = 0, normb = 0, dotprod = 0; + Unqual!(typeof(return)) norma = 0, normb = 0, dotprod = 0; for (; !a.empty; a.popFront(), b.popFront()) { immutable t1 = a.front, t2 = b.front; @@ -1360,13 +1572,15 @@ cosineSimilarity(Range1, Range2)(Range1 a, Range2 b) unittest { - double[] a = [ 1.0, 2.0, ]; - double[] b = [ 4.0, 3.0, ]; - // writeln(cosineSimilarity(a, b)); - // writeln(10.0 / sqrt(5.0 * 25)); - assert(approxEqual( - cosineSimilarity(a, b), 10.0 / sqrt(5.0 * 25), - 0.01)); + import std.typetuple; + foreach(T; TypeTuple!(double, const double, immutable double)) + { + T[] a = [ 1.0, 2.0, ]; + T[] b = [ 4.0, 3.0, ]; + assert(approxEqual( + cosineSimilarity(a, b), 10.0 / sqrt(5.0 * 25), + 0.01)); + } } @@ -1617,7 +1831,8 @@ positive. $(D normalize) assumes that is the case without checking it. Returns: $(D true) if normalization completed normally, $(D false) if all elements in $(D range) were zero or if $(D range) is empty. */ -bool normalize(R)(R range, ElementType!(R) sum = 1) if (isForwardRange!(R)) +bool normalize(R)(R range, ElementType!(R) sum = 1) + if (isForwardRange!(R)) { ElementType!(R) s = 0; // Step 1: Compute sum and length of the range @@ -1651,10 +1866,12 @@ bool normalize(R)(R range, ElementType!(R) sum = 1) if (isForwardRange!(R)) // The path most traveled assert(s >= 0); auto f = sum / s; - foreach (ref e; range) e *= f; + foreach (ref e; range) + e *= f; return true; } +/// unittest { double[] a = []; @@ -1667,6 +1884,45 @@ unittest assert(a == [ 0.5, 0.5 ]); } +/** +Computes accurate sum of binary logarithms of input range $(D r). + */ +ElementType!Range sumOfLog2s(Range)(Range r) + if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) +{ + long exp = 0; + Unqual!(typeof(return)) x = 1; + foreach (e; r) + { + if (e < 0) + return typeof(return).nan; + int lexp = void; + x *= frexp(e, lexp); + exp += lexp; + if (x < 0.5) + { + x *= 2; + exp--; + } + } + return exp + log2(x); +} + +/// +unittest +{ + assert(sumOfLog2s(new double[0]) == 0); + assert(sumOfLog2s([0.0L]) == -real.infinity); + assert(sumOfLog2s([-0.0L]) == -real.infinity); + assert(sumOfLog2s([2.0L]) == 1); + assert(sumOfLog2s([-2.0L]).isNaN()); + assert(sumOfLog2s([real.nan]).isNaN()); + assert(sumOfLog2s([-real.nan]).isNaN()); + assert(sumOfLog2s([real.infinity]) == real.infinity); + assert(sumOfLog2s([-real.infinity]).isNaN()); + assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); +} + /** Computes $(LUCKY _entropy) of input range $(D r) in bits. This function assumes (without checking) that the values in $(D r) are all @@ -1688,10 +1944,10 @@ ElementType!Range entropy(Range)(Range r) if (isInputRange!Range) /// Ditto ElementType!Range entropy(Range, F)(Range r, F max) -if (isInputRange!Range - && !is(CommonType!(ElementType!Range, F) == void)) +if (isInputRange!Range && + !is(CommonType!(ElementType!Range, F) == void)) { - typeof(return) result = 0.0; + Unqual!(typeof(return)) result = 0.0; foreach (e; r) { if (!e) continue; @@ -1703,11 +1959,15 @@ if (isInputRange!Range unittest { - double[] p = [ 0.0, 0, 0, 1 ]; - assert(entropy(p) == 0); - p = [ 0.25, 0.25, 0.25, 0.25 ]; - assert(entropy(p) == 2); - assert(entropy(p, 1) == 1); + import std.typetuple; + foreach(T; TypeTuple!(double, const double, immutable double)) + { + T[] p = [ 0.0, 0, 0, 1 ]; + assert(entropy(p) == 0); + p = [ 0.25, 0.25, 0.25, 0.25 ]; + assert(entropy(p) == 2); + assert(entropy(p, 1) == 1); + } } /** @@ -1728,7 +1988,7 @@ kullbackLeiblerDivergence(Range1, Range2)(Range1 a, Range2 b) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) enforce(a.length == b.length); - FPTemporary!(typeof(return)) result = 0; + Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { immutable t1 = a.front; @@ -1742,6 +2002,7 @@ kullbackLeiblerDivergence(Range1, Range2)(Range1 a, Range2 b) return result; } +/// unittest { double[] p = [ 0.0, 0, 0, 1 ]; @@ -1768,12 +2029,12 @@ or equal to $(D limit). */ CommonType!(ElementType!Range1, ElementType!Range2) jensenShannonDivergence(Range1, Range2)(Range1 a, Range2 b) - if (isInputRange!Range1 && isInputRange!Range2 - && is(CommonType!(ElementType!Range1, ElementType!Range2))) + if (isInputRange!Range1 && isInputRange!Range2 && + is(CommonType!(ElementType!Range1, ElementType!Range2))) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) enforce(a.length == b.length); - FPTemporary!(typeof(return)) result = 0; + Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { immutable t1 = a.front; @@ -1795,13 +2056,13 @@ jensenShannonDivergence(Range1, Range2)(Range1 a, Range2 b) /// Ditto CommonType!(ElementType!Range1, ElementType!Range2) jensenShannonDivergence(Range1, Range2, F)(Range1 a, Range2 b, F limit) - if (isInputRange!Range1 && isInputRange!Range2 - && is(typeof(CommonType!(ElementType!Range1, ElementType!Range2).init + if (isInputRange!Range1 && isInputRange!Range2 && + is(typeof(CommonType!(ElementType!Range1, ElementType!Range2).init >= F.init) : bool)) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) enforce(a.length == b.length); - FPTemporary!(typeof(return)) result = 0; + Unqual!(typeof(return)) result = 0; limit *= 2; for (; !a.empty; a.popFront(), b.popFront()) { @@ -1822,6 +2083,7 @@ jensenShannonDivergence(Range1, Range2, F)(Range1 a, Range2 b, F limit) return result / 2; } +/// unittest { double[] p = [ 0.0, 0, 0, 1 ]; @@ -1835,52 +2097,6 @@ unittest assert(approxEqual(jensenShannonDivergence(p2, p1, 0.005), 0.00602366)); } -// template tabulateFixed(alias fun, uint n, -// real maxError, real left, real right) -// { -// ReturnType!(fun) tabulateFixed(ParameterTypeTuple!(fun) arg) -// { -// alias num = ParameterTypeTuple!(fun)[0]; -// static num[n] table; -// alias x = arg[0]; -// enforce(left <= x && x < right); -// immutable i = cast(uint) (table.length -// * ((x - left) / (right - left))); -// assert(i < n); -// if (isnan(table[i])) { -// // initialize it -// auto x1 = left + i * (right - left) / n; -// auto x2 = left + (i + 1) * (right - left) / n; -// immutable y1 = fun(x1), y2 = fun(x2); -// immutable y = 2 * y1 * y2 / (y1 + y2); -// num wyda(num xx) { return fun(xx) - y; } -// auto bestX = findRoot(&wyda, x1, x2); -// table[i] = fun(bestX); -// immutable leftError = abs((table[i] - y1) / y1); -// enforce(leftError <= maxError, text(leftError, " > ", maxError)); -// immutable rightError = abs((table[i] - y2) / y2); -// enforce(rightError <= maxError, text(rightError, " > ", maxError)); -// } -// return table[i]; -// } -// } - -// unittest -// { -// enum epsilon = 0.01; -// alias fasttanh = tabulateFixed!(tanh, 700, epsilon, 0.2, 3); -// uint testSize = 100000; -// auto rnd = Random(unpredictableSeed); -// foreach (i; 0 .. testSize) { -// immutable x = uniform(rnd, 0.2F, 3.0F); -// immutable float y = fasttanh(x), w = tanh(x); -// immutable e = abs(y - w) / w; -// //writefln("%.20f", e); -// enforce(e <= epsilon, text("x = ", x, ", fasttanh(x) = ", y, -// ", tanh(x) = ", w, ", relerr = ", e)); -// } -// } - /** The so-called "all-lengths gap-weighted string kernel" computes a similarity measure between $(D s) and $(D t) based on all of their @@ -1953,11 +2169,16 @@ t.length)) extra bytes of memory and $(BIGOH s.length * t.length) time to complete. */ F gapWeightedSimilarity(alias comp = "a == b", R1, R2, F)(R1 s, R2 t, F lambda) - if (isRandomAccessRange!(R1) && hasLength!(R1) - && isRandomAccessRange!(R2) && hasLength!(R2)) + if (isRandomAccessRange!(R1) && hasLength!(R1) && + isRandomAccessRange!(R2) && hasLength!(R2)) { + import std.functional : binaryFun; + import std.algorithm : swap; + import core.stdc.stdlib; + if (s.length < t.length) return gapWeightedSimilarity(t, s, lambda); if (!t.length) return 0; + immutable tl1 = t.length + 1; auto dpvi = enforce(cast(F*) malloc(F.sizeof * 2 * t.length)); auto dpvi1 = dpvi + t.length; @@ -1984,8 +2205,8 @@ F gapWeightedSimilarity(alias comp = "a == b", R1, R2, F)(R1 s, R2 t, F lambda) } immutable j1 = j + 1; if (j1 == t.length) break; - dpvi1[j1] = dpsij + lambda * (dpvi1[j] + dpvi[j1]) - - lambda2 * dpvi[j]; + dpvi1[j1] = dpsij + lambda * (dpvi1[j] + dpvi[j1]) - + lambda2 * dpvi[j]; j = j1; } swap(dpvi, dpvi1); @@ -2017,16 +2238,6 @@ so-called normalized kernel) is bounded in $(D [0, 1]), reaches $(D 0) only for ranges that don't match in any position, and $(D 1) only for identical ranges. -Example: ----- -string[] s = ["Hello", "brave", "new", "world"]; -string[] t = ["Hello", "new", "world"]; -assert(gapWeightedSimilarity(s, s, 1) == 15); -assert(gapWeightedSimilarity(t, t, 1) == 7); -assert(gapWeightedSimilarity(s, t, 1) == 7); -assert(gapWeightedSimilarityNormalized(s, t, 1) == 7. / sqrt(15. * 7)); ----- - The optional parameters $(D sSelfSim) and $(D tSelfSim) are meant for avoiding duplicate computation. Many applications may have already computed $(D gapWeightedSimilarity(s, s, lambda)) and/or $(D @@ -2034,16 +2245,17 @@ gapWeightedSimilarity(t, t, lambda)). In that case, they can be passed as $(D sSelfSim) and $(D tSelfSim), respectively. */ Select!(isFloatingPoint!(F), F, double) -gapWeightedSimilarityNormalized -(alias comp = "a == b", R1, R2, F)(R1 s, R2 t, F lambda, - F sSelfSim = F.init, F tSelfSim = F.init) - if (isRandomAccessRange!(R1) && hasLength!(R1) - && isRandomAccessRange!(R2) && hasLength!(R2)) +gapWeightedSimilarityNormalized(alias comp = "a == b", R1, R2, F) + (R1 s, R2 t, F lambda, F sSelfSim = F.init, F tSelfSim = F.init) + if (isRandomAccessRange!(R1) && hasLength!(R1) && + isRandomAccessRange!(R2) && hasLength!(R2)) { static bool uncomputed(F n) { - static if (isFloatingPoint!(F)) return isnan(n); - else return n == n.init; + static if (isFloatingPoint!(F)) + return isNaN(n); + else + return n == n.init; } if (uncomputed(sSelfSim)) sSelfSim = gapWeightedSimilarity!(comp)(s, s, lambda); @@ -2051,10 +2263,12 @@ gapWeightedSimilarityNormalized if (uncomputed(tSelfSim)) tSelfSim = gapWeightedSimilarity!(comp)(t, t, lambda); if (tSelfSim == 0) return 0; - return gapWeightedSimilarity!(comp)(s, t, lambda) - / sqrt(cast(typeof(return)) sSelfSim * tSelfSim); + + return gapWeightedSimilarity!(comp)(s, t, lambda) / + sqrt(cast(typeof(return)) sSelfSim * tSelfSim); } +/// unittest { string[] s = ["Hello", "brave", "new", "world"]; @@ -2073,19 +2287,6 @@ of length 2, and so on. The memory requirement is $(BIGOH s.length * t.length). The time complexity is $(BIGOH s.length * t.length) time for computing each step. Continuing on the previous example: ----- -string[] s = ["Hello", "brave", "new", "world"]; -string[] t = ["Hello", "new", "world"]; -auto simIter = gapWeightedSimilarityIncremental(s, t, 1); -assert(simIter.front == 3); // three 1-length matches -simIter.popFront(); -assert(simIter.front == 3); // three 2-length matches -simIter.popFront(); -assert(simIter.front == 1); // one 3-length match -simIter.popFront(); -assert(simIter.empty); // no more match ----- - The implementation is based on the pseudocode in Fig. 4 of the paper $(WEB jmlr.csail.mit.edu/papers/volume6/rousu05a/rousu05a.pdf, "Efficient Computation of Gapped Substring Kernels on Large Alphabets") @@ -2095,10 +2296,12 @@ optimizations. struct GapWeightedSimilarityIncremental(Range, F = double) if (isRandomAccessRange!(Range) && hasLength!(Range)) { + import core.stdc.stdlib; + private: Range s, t; F currentValue = 0; - F * kl; + F* kl; size_t gram = void; F lambda = void, lambda2 = void; @@ -2108,7 +2311,8 @@ Constructs an object given two ranges $(D s) and $(D t) and a penalty $(D lambda). Constructor completes in $(BIGOH s.length * t.length) time and computes all matches of length 1. */ - this(Range s, Range t, F lambda) { + this(Range s, Range t, F lambda) + { enforce(lambda > 0); this.gram = 0; this.lambda = lambda; @@ -2121,12 +2325,14 @@ time and computes all matches of length 1. size_t k0len; scope(exit) free(k0); currentValue = 0; - foreach (i, si; s) { - foreach (j; 0 .. t.length) { + foreach (i, si; s) + { + foreach (j; 0 .. t.length) + { if (si != t[j]) continue; - k0 = cast(typeof(k0)) - realloc(k0, ++k0len * (*k0).sizeof); - with (k0[k0len - 1]) { + k0 = cast(typeof(k0)) realloc(k0, ++k0len * (*k0).sizeof); + with (k0[k0len - 1]) + { field[0] = i; field[1] = j; } @@ -2152,26 +2358,31 @@ time and computes all matches of length 1. kl = errnoEnforce(cast(F *) malloc(s.length * t.length * F.sizeof)); kl[0 .. s.length * t.length] = 0; - foreach (pos; 0 .. k0len) { - with (k0[pos]) { + foreach (pos; 0 .. k0len) + { + with (k0[pos]) + { kl[(field[0] - iMin) * t.length + field[1] -jMin] = lambda2; } } } -/** -Returns $(D this). - */ + /** + Returns: $(D this). + */ ref GapWeightedSimilarityIncremental opSlice() { return this; } -/** -Computes the match of the popFront length. Completes in $(BIGOH s.length * -t.length) time. - */ - void popFront() { + /** + Computes the match of the popFront length. Completes in $(BIGOH s.length * + t.length) time. + */ + void popFront() + { + import std.algorithm : swap; + // This is a large source of optimization: if similarity at // the gram-1 level was 0, then we can safely assume // similarity at the gram level is 0 as well. @@ -2214,14 +2425,16 @@ t.length) time. { Si_1[0 .. t.length] = 0; kl[0 .. min(t.length, maxPerimeter + 1)] = 0; - foreach (i; 1 .. min(s.length, maxPerimeter + 1)) { + foreach (i; 1 .. min(s.length, maxPerimeter + 1)) + { auto kli = kl + i * t.length; assert(s.length > i); const si = s[i]; auto kl_1i_1 = kl_1 + (i - 1) * t.length; kli[0] = 0; F lastS = 0; - foreach (j; 1 .. min(maxPerimeter - i + 1, t.length)) { + foreach (j; 1 .. min(maxPerimeter - i + 1, t.length)) + { immutable j_1 = j - 1; immutable tmp = kl_1i_1[j_1] + lambda * (Si_1[j] + lastS) @@ -2229,9 +2442,12 @@ t.length) time. kl_1i_1[j_1] = float.nan; Si_1[j_1] = lastS; lastS = tmp; - if (si == t[j]) { + if (si == t[j]) + { currentValue += kli[j] = lambda2 * lastS; - } else { + } + else + { kli[j] = 0; } } @@ -2243,18 +2459,20 @@ t.length) time. } } -/** -Returns the gapped similarity at the current match length (initially -1, grows with each call to $(D popFront)). - */ + /** + Returns: The gapped similarity at the current match length (initially + 1, grows with each call to $(D popFront)). + */ @property F front() { return currentValue; } -/** -Returns whether there are more matches. - */ - @property bool empty() { + /** + Returns: Whether there are more matches. + */ + @property bool empty() + { if (currentValue) return false; - if (kl) { + if (kl) + { free(kl); kl = null; } @@ -2271,8 +2489,24 @@ GapWeightedSimilarityIncremental!(R, F) gapWeightedSimilarityIncremental(R, F) return typeof(return)(r1, r2, penalty); } +/// +unittest +{ + string[] s = ["Hello", "brave", "new", "world"]; + string[] t = ["Hello", "new", "world"]; + auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0); + assert(simIter.front == 3); // three 1-length matches + simIter.popFront(); + assert(simIter.front == 3); // three 2-length matches + simIter.popFront(); + assert(simIter.front == 1); // one 3-length match + simIter.popFront(); + assert(simIter.empty); // no more match +} + unittest { + import std.conv: text; string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0); @@ -2344,14 +2578,20 @@ unittest Computes the greatest common divisor of $(D a) and $(D b) by using Euclid's algorithm. */ -T gcd(T)(T a, T b) { - static if (is(T == const) || is(T == immutable)) { +T gcd(T)(T a, T b) +{ + static if (is(T == const) || is(T == immutable)) + { return gcd!(Unqual!T)(a, b); - } else { - static if (T.min < 0) { + } + else + { + static if (T.min < 0) + { enforce(a >= 0 && b >=0); } - while (b) { + while (b) + { auto t = b; b = a % b; a = t; @@ -2360,69 +2600,14 @@ T gcd(T)(T a, T b) { } } -unittest { +/// +unittest +{ assert(gcd(2 * 5 * 7 * 7, 5 * 7 * 11) == 5 * 7); const int a = 5 * 13 * 23 * 23, b = 13 * 59; assert(gcd(a, b) == 13); } -/* - * Copyright (C) 2004-2009 by Digital Mars, www.digitalmars.com - * Written by Andrei Alexandrescu, www.erdani.org - * - * 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: - * - * o 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. - * o Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * o This notice may not be removed or altered from any source - * distribution. - */ -/+ -/** -Primes generator -*/ -struct Primes(UIntType) -{ - private UIntType[] found = [ 2 ]; - - UIntType front() { return found[$ - 1]; } - - void popFront() - { - outer: - for (UIntType candidate = front + 1 + (front != 2); ; candidate += 2) - { - UIntType stop = cast(uint) sqrt(cast(double) candidate); - foreach (e; found) - { - if (e > stop) break; - if (candidate % e == 0) continue outer; - } - // found! - found ~= candidate; - break; - } - } - - enum bool empty = false; -} - -unittest -{ - foreach (e; take(10, Primes!(uint)())) writeln(e); -} -+/ - // This is to make tweaking the speed/size vs. accuracy tradeoff easy, // though floats seem accurate enough for all practical purposes, since // they pass the "approxEqual(inverseFft(fft(arr)), arr)" test even for @@ -2440,28 +2625,41 @@ private alias lookup_t = float; * References: * $(WEB en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm) */ -final class Fft { +final class Fft +{ + import std.algorithm : map; + import core.bitop : bsf; + import std.array : uninitializedArray; + private: immutable lookup_t[][] negSinLookup; - void enforceSize(R)(R range) const { + void enforceSize(R)(R range) const + { + import std.conv: text; enforce(range.length <= size, text( "FFT size mismatch. Expected ", size, ", got ", range.length)); } void fftImpl(Ret, R)(Stride!R range, Ret buf) const - in { + in + { assert(range.length >= 4); assert(isPowerOfTwo(range.length)); - } body { + } + body + { auto recurseRange = range; recurseRange.doubleSteps(); - if(buf.length > 4) { + if(buf.length > 4) + { fftImpl(recurseRange, buf[0..$ / 2]); recurseRange.popHalf(); fftImpl(recurseRange, buf[$ / 2..$]); - } else { + } + else + { // Do this here instead of in another recursion to save on // recursion overhead. slowFourier2(recurseRange, buf[0..$ / 2]); @@ -2478,63 +2676,80 @@ private: // by making the odd terms into the imaginary components of our new FFT, // and then using symmetry to recombine them. void fftImplPureReal(Ret, R)(R range, Ret buf) const - in { + in + { assert(range.length >= 4); assert(isPowerOfTwo(range.length)); - } body { + } + body + { alias E = ElementType!R; // Converts odd indices of range to the imaginary components of // a range half the size. The even indices become the real components. - static if(isArray!R && isFloatingPoint!E) { + static if(isArray!R && isFloatingPoint!E) + { // Then the memory layout of complex numbers provides a dirt // cheap way to convert. This is a common case, so take advantage. auto oddsImag = cast(Complex!E[]) range; - } else { + } + else + { // General case: Use a higher order range. We can assume // source.length is even because it has to be a power of 2. - static struct OddToImaginary { + static struct OddToImaginary + { R source; alias C = Complex!(CommonType!(E, typeof(buf[0].re))); - @property { - C front() { + @property + { + C front() + { return C(source[0], source[1]); } - C back() { + C back() + { immutable n = source.length; return C(source[n - 2], source[n - 1]); } - typeof(this) save() { + typeof(this) save() + { return typeof(this)(source.save); } - bool empty() { + bool empty() + { return source.empty; } - size_t length() { + size_t length() + { return source.length / 2; } } - void popFront() { + void popFront() + { source.popFront(); source.popFront(); } - void popBack() { + void popBack() + { source.popBack(); source.popBack(); } - C opIndex(size_t index) { + C opIndex(size_t index) + { return C(source[index * 2], source[index * 2 + 1]); } - typeof(this) opSlice(size_t lower, size_t upper) { + typeof(this) opSlice(size_t lower, size_t upper) + { return typeof(this)(source[lower * 2..upper * 2]); } } @@ -2551,7 +2766,8 @@ private: evenFft[0].im = 0; // evenFft[0].re is already right b/c it's aliased with buf[0].re. - foreach(k; 1..halfN / 2 + 1) { + foreach (k; 1..halfN / 2 + 1) + { immutable bufk = buf[k]; immutable bufnk = buf[buf.length / 2 - k]; evenFft[k].re = 0.5 * (bufk.re + bufnk.re); @@ -2569,9 +2785,12 @@ private: } void butterfly(R)(R buf) const - in { + in + { assert(isPowerOfTwo(buf.length)); - } body { + } + body + { immutable n = buf.length; immutable localLookup = negSinLookup[bsf(n)]; assert(localLookup.length == n); @@ -2579,11 +2798,13 @@ private: immutable cosMask = n - 1; immutable cosAdd = n / 4 * 3; - lookup_t negSinFromLookup(size_t index) pure nothrow { + lookup_t negSinFromLookup(size_t index) pure nothrow + { return localLookup[index]; } - lookup_t cosFromLookup(size_t index) pure nothrow { + lookup_t cosFromLookup(size_t index) pure nothrow + { // cos is just -sin shifted by PI * 3 / 2. return localLookup[(index + cosAdd) & cosMask]; } @@ -2593,7 +2814,8 @@ private: // This loop is unrolled and the two iterations are interleaved // relative to the textbook FFT to increase ILP. This gives roughly 5% // speedups on DMD. - for(size_t k = 0; k < halfLen; k += 2) { + for (size_t k = 0; k < halfLen; k += 2) + { immutable cosTwiddle1 = cosFromLookup(k); immutable sinTwiddle1 = negSinFromLookup(k); immutable cosTwiddle2 = cosFromLookup(k + 1); @@ -2638,7 +2860,8 @@ private: // // Also, this is unsafe because the memSpace buffer will be cast // to immutable. - public this(lookup_t[] memSpace) { // Public b/c of bug 4636. + public this(lookup_t[] memSpace) // Public b/c of bug 4636. + { immutable size = memSpace.length / 2; /* Create a lookup table of all negative sine values at a resolution of @@ -2646,7 +2869,8 @@ private: * inefficient, but having all the lookups be next to each other in * memory at every level of iteration is a huge win performance-wise. */ - if(size == 0) { + if(size == 0) + { return; } @@ -2659,30 +2883,32 @@ private: auto lastRow = table[$ - 1]; lastRow[0] = 0; // -sin(0) == 0. - foreach(ptrdiff_t i; 1..size) { + foreach (ptrdiff_t i; 1..size) + { // The hard coded cases are for improved accuracy and to prevent // annoying non-zeroness when stuff should be zero. - if(i == size / 4) { + if (i == size / 4) lastRow[i] = -1; // -sin(pi / 2) == -1. - } else if(i == size / 2) { + else if (i == size / 2) lastRow[i] = 0; // -sin(pi) == 0. - } else if(i == size * 3 / 4) { + else if (i == size * 3 / 4) lastRow[i] = 1; // -sin(pi * 3 / 2) == 1 - } else { + else lastRow[i] = -sin(i * 2.0L * PI / size); - } } // Fill in all the other rows with strided versions. - foreach(i; 1..table.length - 1) { + foreach (i; 1..table.length - 1) + { immutable strideLength = size / (2 ^^ i); auto strided = Stride!(lookup_t[])(lastRow, strideLength); table[i] = memSpace[$ - strided.length..$]; memSpace = memSpace[0..$ - strided.length]; size_t copyIndex; - foreach(elem; strided) { + foreach (elem; strided) + { table[i][copyIndex++] = elem; } } @@ -2695,14 +2921,16 @@ public: * power of two sizes of $(D size) or smaller. $(D size) must be a * power of two. */ - this(size_t size) { + this(size_t size) + { // Allocate all twiddle factor buffers in one contiguous block so that, // when one is done being used, the next one is next in cache. auto memSpace = uninitializedArray!(lookup_t[])(2 * size); this(memSpace); } - @property size_t size() const { + @property size_t size() const + { return (negSinLookup is null) ? 0 : negSinLookup[$ - 1].length; } @@ -2723,10 +2951,12 @@ public: * i.e., output[j] := sum[ exp(-2 PI i j k / N) input[k] ]. */ Complex!F[] fft(F = double, R)(R range) const - if(isFloatingPoint!F && isRandomAccessRange!R) { + if (isFloatingPoint!F && isRandomAccessRange!R) + { enforceSize(range); Complex!F[] ret; - if(range.length == 0) { + if (range.length == 0) + { return ret; } @@ -2744,33 +2974,44 @@ public: * property that can be both read and written and are floating point numbers. */ void fft(Ret, R)(R range, Ret buf) const - if(isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) { + if(isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) + { enforce(buf.length == range.length); enforceSize(range); - if(range.length == 0) { + if (range.length == 0) + { return; - } else if(range.length == 1) { + } + else if (range.length == 1) + { buf[0] = range[0]; return; - } else if(range.length == 2) { + } + else if (range.length == 2) + { slowFourier2(range, buf); return; - } else { + } + else + { alias E = ElementType!R; - static if(is(E : real)) { + static if (is(E : real)) + { return fftImplPureReal(range, buf); - } else { - static if(is(R : Stride!R)) { + } + else + { + static if (is(R : Stride!R)) return fftImpl(range, buf); - } else { + else return fftImpl(Stride!R(range, 1), buf); - } } } } - /**Computes the inverse Fourier transform of a range. The range must be a + /** + * Computes the inverse Fourier transform of a range. The range must be a * random access range with slicing, have a length equal to the size * provided at construction of this object, and contain elements that are * either of type std.complex.Complex or have essentially @@ -2782,10 +3023,12 @@ public: * output[j] := (1 / N) sum[ exp(+2 PI i j k / N) input[k] ]. */ Complex!F[] inverseFft(F = double, R)(R range) const - if(isRandomAccessRange!R && isComplexLike!(ElementType!R) && isFloatingPoint!F) { + if (isRandomAccessRange!R && isComplexLike!(ElementType!R) && isFloatingPoint!F) + { enforceSize(range); Complex!F[] ret; - if(range.length == 0) { + if (range.length == 0) + { return ret; } @@ -2796,19 +3039,22 @@ public: return ret; } - /**Inverse FFT that allows a user-supplied buffer to be provided. The buffer + /** + * Inverse FFT that allows a user-supplied buffer to be provided. The buffer * must be a random access range with slicing, and its elements * must be some complex-like type. */ void inverseFft(Ret, R)(R range, Ret buf) const - if(isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) { + if (isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) + { enforceSize(range); auto swapped = map!swapRealImag(range); fft(swapped, buf); immutable lenNeg1 = 1.0 / buf.length; - foreach(ref elem; buf) { + foreach (ref elem; buf) + { auto temp = elem.re * lenNeg1; elem.re = elem.im * lenNeg1; elem.im = temp; @@ -2820,9 +3066,12 @@ public: // memory owned by the object is deterministically destroyed at the end of that // scope. private enum string MakeLocalFft = q{ + import core.stdc.stdlib; + import core.exception : OutOfMemoryError; auto lookupBuf = (cast(lookup_t*) malloc(range.length * 2 * lookup_t.sizeof)) [0..2 * range.length]; - if(!lookupBuf.ptr) { + if (!lookupBuf.ptr) + { throw new OutOfMemoryError(__FILE__, __LINE__); } scope(exit) free(cast(void*) lookupBuf.ptr); @@ -2837,30 +3086,38 @@ private enum string MakeLocalFft = q{ * as the Fft object is deterministically destroyed before these * functions return. */ -Complex!F[] fft(F = double, R)(R range) { +Complex!F[] fft(F = double, R)(R range) +{ mixin(MakeLocalFft); return fftObj.fft!(F, R)(range); } /// ditto -void fft(Ret, R)(R range, Ret buf) { +void fft(Ret, R)(R range, Ret buf) +{ mixin(MakeLocalFft); return fftObj.fft!(Ret, R)(range, buf); } /// ditto -Complex!F[] inverseFft(F = double, R)(R range) { +Complex!F[] inverseFft(F = double, R)(R range) +{ mixin(MakeLocalFft); return fftObj.inverseFft!(F, R)(range); } /// ditto -void inverseFft(Ret, R)(R range, Ret buf) { +void inverseFft(Ret, R)(R range, Ret buf) +{ mixin(MakeLocalFft); return fftObj.inverseFft!(Ret, R)(range, buf); } -unittest { +unittest +{ + import std.algorithm; + import std.range; + import std.conv; // Test values from R and Octave. auto arr = [1,2,3,4,5,6,7,8]; auto fft1 = fft(arr); @@ -2928,7 +3185,8 @@ unittest { // Swaps the real and imaginary parts of a complex number. This is useful // for inverse FFTs. -C swapRealImag(C)(C input) { +C swapRealImag(C)(C input) +{ return C(input.im, input.re); } @@ -2936,65 +3194,81 @@ private: // The reasons I couldn't use std.algorithm were b/c its stride length isn't // modifiable on the fly and because range has grown some performance hacks // for powers of 2. -struct Stride(R) { +struct Stride(R) +{ + import core.bitop : bsf; Unqual!R range; size_t _nSteps; size_t _length; alias E = ElementType!(R); - this(R range, size_t nStepsIn) { + this(R range, size_t nStepsIn) + { this.range = range; _nSteps = nStepsIn; _length = (range.length + _nSteps - 1) / nSteps; } - size_t length() const @property { + size_t length() const @property + { return _length; } - typeof(this) save() @property { + typeof(this) save() @property + { auto ret = this; ret.range = ret.range.save; return ret; } - E opIndex(size_t index) { + E opIndex(size_t index) + { return range[index * _nSteps]; } - E front() @property { + E front() @property + { return range[0]; } - void popFront() { - if(range.length >= _nSteps) { + void popFront() + { + if (range.length >= _nSteps) + { range = range[_nSteps..range.length]; _length--; - } else { + } + else + { range = range[0..0]; _length = 0; } } // Pops half the range's stride. - void popHalf() { + void popHalf() + { range = range[_nSteps / 2..range.length]; } - bool empty() const @property { + bool empty() const @property + { return length == 0; } - size_t nSteps() const @property { + size_t nSteps() const @property + { return _nSteps; } - void doubleSteps() { + void doubleSteps() + { _nSteps *= 2; _length /= 2; } - size_t nSteps(size_t newVal) @property { + size_t nSteps(size_t newVal) @property + { _nSteps = newVal; // Using >> bsf(nSteps) is a few cycles faster than / nSteps. @@ -3006,7 +3280,8 @@ struct Stride(R) { // Hard-coded base case for FFT of size 2. This is actually a TON faster than // using a generic slow DFT. This seems to be the best base case. (Size 1 // can be coded inline as buf[0] = range[0]). -void slowFourier2(Ret, R)(R range, Ret buf) { +void slowFourier2(Ret, R)(R range, Ret buf) +{ assert(range.length == 2); assert(buf.length == 2); buf[0] = range[0] + range[1]; @@ -3015,7 +3290,8 @@ void slowFourier2(Ret, R)(R range, Ret buf) { // Hard-coded base case for FFT of size 4. Doesn't work as well as the size // 2 case. -void slowFourier4(Ret, R)(R range, Ret buf) { +void slowFourier4(Ret, R)(R range, Ret buf) +{ alias C = ElementType!Ret; assert(range.length == 4); @@ -3026,26 +3302,32 @@ void slowFourier4(Ret, R)(R range, Ret buf) { buf[3] = range[0] + range[1] * C(0, 1) - range[2] - range[3] * C(0, 1); } -bool isPowerOfTwo(size_t num) { +bool isPowerOfTwo(size_t num) +{ + import core.bitop : bsf, bsr; return bsr(num) == bsf(num); } -size_t roundDownToPowerOf2(size_t num) { +size_t roundDownToPowerOf2(size_t num) +{ + import core.bitop : bsr; return num & (1 << bsr(num)); } -unittest { +unittest +{ assert(roundDownToPowerOf2(7) == 4); assert(roundDownToPowerOf2(4) == 4); } -template isComplexLike(T) { +template isComplexLike(T) +{ enum bool isComplexLike = is(typeof(T.init.re)) && is(typeof(T.init.im)); } -unittest { +unittest +{ static assert(isComplexLike!(Complex!double)); static assert(!isComplexLike!(uint)); } - diff --git a/std/outbuffer.d b/std/outbuffer.d index fc44dc17e51..e15229bca4e 100644 --- a/std/outbuffer.d +++ b/std/outbuffer.d @@ -1,29 +1,26 @@ // Written in the D programming language. /** +Serialize data to $(D ubyte) arrays. + * Macros: * WIKI = Phobos/StdOutbuffer * - * Copyright: Copyright Digital Mars 2000 - 2009. - * License: Boost License 1.0. + * Copyright: Copyright Digital Mars 2000 - 2015. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * Source: $(PHOBOSSRC std/_outbuffer.d) */ -/* Copyright Digital Mars 2000 - 2009. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE_1_0.txt or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ module std.outbuffer; private { import core.memory; + import core.stdc.stdarg; + import core.stdc.stdio; + import core.stdc.stdlib; import std.algorithm; import std.string; - import std.c.stdio; - import std.c.stdlib; - import std.c.stdarg; } /********************************************* @@ -47,6 +44,8 @@ class OutBuffer assert(offset <= data.length); } + pure nothrow @safe + { this() { //printf("in OutBuffer constructor\n"); @@ -67,7 +66,7 @@ class OutBuffer */ - void reserve(size_t nbytes) + void reserve(size_t nbytes) @trusted in { assert(offset + nbytes >= offset); @@ -102,12 +101,12 @@ class OutBuffer offset += bytes.length; } - void write(in wchar[] chars) + void write(in wchar[] chars) @trusted { write(cast(ubyte[]) chars); } - void write(const(dchar)[] chars) + void write(const(dchar)[] chars) @trusted { write(cast(ubyte[]) chars); } @@ -123,7 +122,7 @@ class OutBuffer void write(char c) { write(cast(ubyte)c); } /// ditto void write(dchar c) { write(cast(uint)c); } /// ditto - void write(ushort w) /// ditto + void write(ushort w) @trusted /// ditto { reserve(ushort.sizeof); *cast(ushort *)&data[offset] = w; @@ -132,14 +131,14 @@ class OutBuffer void write(short s) { write(cast(ushort)s); } /// ditto - void write(wchar c) /// ditto + void write(wchar c) @trusted /// ditto { reserve(wchar.sizeof); *cast(wchar *)&data[offset] = c; offset += wchar.sizeof; } - void write(uint w) /// ditto + void write(uint w) @trusted /// ditto { reserve(uint.sizeof); *cast(uint *)&data[offset] = w; @@ -148,7 +147,7 @@ class OutBuffer void write(int i) { write(cast(uint)i); } /// ditto - void write(ulong l) /// ditto + void write(ulong l) @trusted /// ditto { reserve(ulong.sizeof); *cast(ulong *)&data[offset] = l; @@ -157,35 +156,31 @@ class OutBuffer void write(long l) { write(cast(ulong)l); } /// ditto - void write(float f) /// ditto + void write(float f) @trusted /// ditto { reserve(float.sizeof); *cast(float *)&data[offset] = f; offset += float.sizeof; } - void write(double f) /// ditto + void write(double f) @trusted /// ditto { reserve(double.sizeof); *cast(double *)&data[offset] = f; offset += double.sizeof; } - void write(real f) /// ditto + void write(real f) @trusted /// ditto { reserve(real.sizeof); *cast(real *)&data[offset] = f; offset += real.sizeof; } - void write(in char[] s) /// ditto + void write(in char[] s) @trusted /// ditto { write(cast(ubyte[])s); } - // void write(immutable(char)[] s) /// ditto - // { - // write(cast(ubyte[])s); - // } void write(OutBuffer buf) /// ditto { @@ -249,17 +244,18 @@ class OutBuffer * Convert internal buffer to array of chars. */ - override string toString() + override string toString() const { //printf("OutBuffer.toString()\n"); return cast(string) data[0 .. offset].idup; } + } /***************************************** * Append output of C's vprintf() to internal buffer. */ - void vprintf(string format, va_list args) + void vprintf(string format, va_list args) @trusted nothrow { char[128] buffer; int count; @@ -314,26 +310,12 @@ class OutBuffer * Append output of C's printf() to internal buffer. */ - void printf(string format, ...) + void printf(string format, ...) @trusted { - version (Win64) - { - vprintf(format, _argptr); - } - else version (X86_64) - { - va_list ap; - va_start(ap, __va_argsave); - vprintf(format, ap); - va_end(ap); - } - else - { - va_list ap; - ap = cast(va_list)&format; - ap += format.sizeof; - vprintf(format, ap); - } + va_list ap; + va_start(ap, format); + vprintf(format, ap); + va_end(ap); } /***************************************** @@ -341,7 +323,7 @@ class OutBuffer * all data past index. */ - void spread(size_t index, size_t nbytes) + void spread(size_t index, size_t nbytes) pure nothrow @safe in { assert(index <= offset); diff --git a/std/parallelism.d b/std/parallelism.d index 79312035975..1590c255818 100644 --- a/std/parallelism.d +++ b/std/parallelism.d @@ -147,6 +147,15 @@ else version(linux) totalCPUs = cast(uint) sysconf(_SC_NPROCESSORS_ONLN); } } +else version(Solaris) +{ + import core.sys.posix.unistd; + + shared static this() + { + totalCPUs = cast(uint) sysconf(_SC_NPROCESSORS_ONLN); + } +} else version(Android) { import core.sys.posix.unistd; @@ -195,20 +204,23 @@ else without wrapping it. If I didn't wrap it, casts would be required basically everywhere. */ -private void atomicSetUbyte(ref ubyte stuff, ubyte newVal) +private void atomicSetUbyte(T)(ref T stuff, T newVal) +if (__traits(isIntegral, T) && is(T : ubyte)) { //core.atomic.cas(cast(shared) &stuff, stuff, newVal); atomicStore(*(cast(shared) &stuff), newVal); } -private ubyte atomicReadUbyte(ref ubyte val) +private ubyte atomicReadUbyte(T)(ref T val) +if (__traits(isIntegral, T) && is(T : ubyte)) { return atomicLoad(*(cast(shared) &val)); } // This gets rid of the need for a lot of annoying casts in other parts of the // code, when enums are involved. -private bool atomicCasUbyte(ref ubyte stuff, ubyte testVal, ubyte newVal) +private bool atomicCasUbyte(T)(ref T stuff, T testVal, T newVal) +if (__traits(isIntegral, T) && is(T : ubyte)) { return core.atomic.cas(cast(shared) &stuff, testVal, newVal); } @@ -4187,7 +4199,7 @@ unittest void next(ref char[] buf) { file.readln(buf); - import std.string; + import std.string : chomp; buf = chomp(buf); } diff --git a/std/path.d b/std/path.d index 46ec8843256..254d8f0001c 100644 --- a/std/path.d +++ b/std/path.d @@ -32,11 +32,15 @@ returned, it is usually a slice of an input string. If a function allocates, this is explicitly mentioned in the documentation. + Upgrading: + $(WEB digitalmars.com/d/1.0/phobos/std_path.html#fnmatch) can + be replaced with $(D globMatch). + Authors: Lars Tandle Kyllingstad, $(WEB digitalmars.com, Walter Bright), Grzegorz Adam Hankiewicz, - Thomas Kühne, + Thomas K$(UUML)hne, $(WEB erdani.org, Andrei Alexandrescu) Copyright: Copyright (c) 2000-2014, the authors. All rights reserved. @@ -50,26 +54,11 @@ module std.path; -import std.algorithm; -import std.array; -import std.conv; -import std.file: getcwd; -import std.range; -import std.string; +// FIXME +import std.file; //: getcwd; +import std.range.primitives; import std.traits; -version(Posix) -{ - import core.exception; - import core.stdc.errno; - import core.sys.posix.pwd; - import core.sys.posix.stdlib; - private import core.exception : onOutOfMemoryError; -} - - - - /** String used to separate directory names in a path. Under POSIX this is a slash, under Windows a backslash. */ @@ -420,6 +409,7 @@ C[] dirName(C)(C[] path) //TODO: @safe (BUG 6169) pure nothrow (because of to()) if (isSomeChar!C) { + import std.conv : to; if (path.empty) return to!(typeof(return))("."); auto p = rtrimDirSeparators(path); @@ -924,6 +914,7 @@ immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext) @trusted pure // TODO: nothrow (because of to()) if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) { + import std.conv : to; auto i = extSeparatorPos(path); if (i == -1) { @@ -1059,6 +1050,7 @@ unittest unittest // non-documented { + import std.range; // ir() wraps an array in a plain (i.e. non-forward) input range, so that // we can test both code paths InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); } @@ -1154,14 +1146,19 @@ unittest while at the same time resolving current/parent directory symbols ($(D ".") and $(D "..")) and removing superfluous directory separators. + It will return "." if the path leads to the starting directory. On Windows, slashes are replaced with backslashes. + Using buildNormalizedPath on null paths will always return null. + Note that this function does not resolve symbolic links. This function always allocates memory to hold the resulting path. Examples: --- + assert (buildNormalizedPath("foo", "..") == "."); + version (Posix) { assert (buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz"); @@ -1186,7 +1183,20 @@ immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...) @trusted pure nothrow if (isSomeChar!C) { - import std.c.stdlib; + import core.stdc.stdlib; + + //Remove empty fields + bool allEmpty = true; + foreach (ref const(C[]) path ; paths) + { + if (path !is null) + { + allEmpty = false; + break; + } + } + if (allEmpty) return null; + auto paths2 = new const(C)[][](paths.length); //(cast(const(C)[]*)alloca((const(C)[]).sizeof * paths.length))[0 .. paths.length]; @@ -1367,7 +1377,14 @@ immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...) // Return path, including root element and excluding the // final dir separator. immutable len = (relPart.ptr - fullPath.ptr) + (i > 0 ? i - 1 : 0); - fullPath = fullPath[0 .. len]; + if (len == 0) + { + fullPath.length = 1; + fullPath[0] = '.'; + } + else + fullPath = fullPath[0 .. len]; + version (Windows) { // On Windows, if the path is on the form `\\server\share`, @@ -1384,6 +1401,18 @@ unittest { assert (buildNormalizedPath("") is null); assert (buildNormalizedPath("foo") == "foo"); + assert (buildNormalizedPath(".") == "."); + assert (buildNormalizedPath(".", ".") == "."); + assert (buildNormalizedPath("foo", "..") == "."); + assert (buildNormalizedPath("", "") is null); + assert (buildNormalizedPath("", ".") == "."); + assert (buildNormalizedPath(".", "") == "."); + assert (buildNormalizedPath(null, "foo") == "foo"); + assert (buildNormalizedPath("", "foo") == "foo"); + assert (buildNormalizedPath("", "") == ""); + assert (buildNormalizedPath("", null) == ""); + assert (buildNormalizedPath(null, "") == ""); + assert (buildNormalizedPath!(char)(null, null) == ""); version (Posix) { @@ -1406,6 +1435,15 @@ unittest assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz"); assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz"); assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz"); + + assert (buildNormalizedPath("foo", "", "bar") == "foo/bar"); + assert (buildNormalizedPath("foo", null, "bar") == "foo/bar"); + + //Curent dir path + assert (buildNormalizedPath("./") == "."); + assert (buildNormalizedPath("././") == "."); + assert (buildNormalizedPath("./foo/..") == "."); + assert (buildNormalizedPath("foo/..") == "."); } else version (Windows) { @@ -1452,6 +1490,15 @@ unittest assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`); assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`); assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`); + + assert (buildNormalizedPath("foo", "", "bar") == `foo\bar`); + assert (buildNormalizedPath("foo", null, "bar") == `foo\bar`); + + //Curent dir path + assert (buildNormalizedPath(`.\`) == "."); + assert (buildNormalizedPath(`.\.\`) == "."); + assert (buildNormalizedPath(`.\foo\..`) == "."); + assert (buildNormalizedPath(`foo\..`) == "."); } else static assert (0); } @@ -1737,6 +1784,7 @@ unittest // equal2 verifies that the range is the same both ways, i.e. // through front/popFront and back/popBack. import std.range; + import std.algorithm; bool equal2(R1, R2)(R1 r1, R2 r2) { static assert (isBidirectionalRange!R1); @@ -2384,6 +2432,7 @@ bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C) in { // Verify that pattern[] is valid + import std.algorithm : balancedParens; assert(balancedParens(pattern, '[', ']', 0)); assert(balancedParens(pattern, '{', '}', 0)); } @@ -2573,7 +2622,7 @@ bool isValidFilename(R)(R filename) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || is(StringTypeOf!R)) { - import core.stdc.stdio; + import core.stdc.stdio : FILENAME_MAX; if (filename.length == 0 || filename.length >= FILENAME_MAX) return false; foreach (c; filename) { @@ -2618,6 +2667,7 @@ bool isValidFilename(R)(R filename) unittest { + import std.conv; auto valid = ["foo"]; auto invalid = ["", "foo\0bar", "foo/bar"]; auto pfdep = [`foo\bar`, "*.txt"]; @@ -2846,6 +2896,9 @@ string expandTilde(string inputPath) { import core.stdc.string : strlen; import core.stdc.stdlib : getenv, malloc, free; + import core.exception : onOutOfMemoryError; + import core.sys.posix.pwd : passwd, getpwnam_r; + import core.stdc.errno : errno, ERANGE; /* Joins a path from a C string to the remainder of path. @@ -2901,12 +2954,14 @@ string expandTilde(string inputPath) } else { + import std.string : indexOf; + assert(path.length > 2 || (path.length == 2 && !isDirSeparator(path[1]))); assert(path[0] == '~'); // Extract username, searching for path separator. string username; - auto last_char = std.string.indexOf(path, dirSeparator[0]); + auto last_char = indexOf(path, dirSeparator[0]); if (last_char == -1) { diff --git a/std/process.d b/std/process.d index 40094d44ca6..5ecf2590cd5 100644 --- a/std/process.d +++ b/std/process.d @@ -75,6 +75,8 @@ Authors: $(WEB thecybershadow.net, Vladimir Panteleev) Copyright: Copyright (c) 2013, the authors. All rights reserved. +License: + $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Source: $(PHOBOSSRC std/_process.d) Macros: @@ -99,13 +101,12 @@ version (Windows) import std.utf; import std.windows.syserror; } -import std.algorithm; -import std.array; + +import std.range.primitives; import std.conv; import std.exception; import std.path; import std.stdio; -import std.string; import std.internal.processinit; import std.internal.cstring; @@ -356,6 +357,9 @@ private Pid spawnProcessImpl(in char[][] args, @trusted // TODO: Should be @safe { import core.exception: RangeError; + import std.path : isDirSeparator; + import std.algorithm : any; + import std.string : toStringz; if (args.empty) throw new RangeError(); const(char)[] name = args[0]; @@ -643,7 +647,8 @@ private LPVOID createEnv(const string[string] childEnv, bool mergeWithParentEnv) { if (mergeWithParentEnv && childEnv.length == 0) return null; - + import std.array : appender; + import std.uni : toUpper; auto envz = appender!(wchar[])(); void put(string var, string val) { @@ -686,6 +691,8 @@ version (Posix) private string searchPathFor(in char[] executable) @trusted //TODO: @safe nothrow { + import std.algorithm : splitter; + auto pathz = core.stdc.stdlib.getenv("PATH"); if (pathz == null) return null; @@ -708,6 +715,7 @@ private bool isExecutable(in char[] path) @trusted nothrow @nogc //TODO: @safe version (Posix) unittest { + import std.algorithm; auto unamePath = searchPathFor("uname"); assert (!unamePath.empty); assert (unamePath[0] == '/'); @@ -795,6 +803,7 @@ unittest // Environment variables in spawnProcess(). unittest // Stream redirection in spawnProcess(). { + import std.string; version (Windows) TestScript prog = "set /p INPUT= echo %INPUT% output %~1 @@ -870,12 +879,13 @@ unittest // Specifying empty working directory. TestScript prog = ""; string directory = ""; - assert(directory && !directory.length); + assert(directory.ptr && !directory.length); spawnProcess([prog.path], null, Config.none, directory).wait(); } unittest // Reopening the standard streams (issue 13258) { + import std.string; void fun() { spawnShell("echo foo").wait(); @@ -990,6 +1000,7 @@ unittest version (Windows) unittest { + import std.string; TestScript prog = "echo %0 %*"; auto outputFn = uniqueTempPath(); scope(exit) if (exists(outputFn)) remove(outputFn); @@ -1083,7 +1094,7 @@ final class Pid This is a number that uniquely identifies the process on the operating system, for at least as long as the process is running. Once $(LREF wait) has been called on the $(LREF Pid), this method will return an - invalid process ID. + invalid (negative) process ID. */ @property int processID() const @safe pure nothrow { @@ -1581,6 +1592,7 @@ private: unittest { + import std.string; auto p = pipe(); p.writeEnd.writeln("Hello World"); p.writeEnd.flush(); @@ -1640,6 +1652,7 @@ $(XREF stdio,StdioException) on failure to redirect any of the streams.$(BR) Example: --- +// my_application writes to stdout and might write to stderr auto pipes = pipeProcess("my_application", Redirect.stdout | Redirect.stderr); scope(exit) wait(pipes.pid); @@ -1650,6 +1663,28 @@ foreach (line; pipes.stdout.byLine) output ~= line.idup; // Store lines of errors. string[] errors; foreach (line; pipes.stderr.byLine) errors ~= line.idup; + + +// sendmail expects to read from stdin +pipes = pipeProcess(["/usr/bin/sendmail", "-t"], Redirect.stdin); +pipes.stdin.writeln("To: you"); +pipes.stdin.writeln("From: me"); +pipes.stdin.writeln("Subject: dlang"); +pipes.stdin.writeln(""); +pipes.stdin.writeln(message); + +// a single period tells sendmail we are finished +pipes.stdin.writeln("."); + +// but at this point sendmail might not see it, we need to flush +pipes.stdin.flush(); + +// sendmail happens to exit on ".", but some you have to close the file: +pipes.stdin.close(); + +// otherwise this wait will wait forever +wait(pipes.pid); + --- */ ProcessPipes pipeProcess(in char[][] args, @@ -1795,6 +1830,7 @@ enum Redirect unittest { + import std.string; version (Windows) TestScript prog = "call :sub %~1 %~2 0 call :sub %~1 %~2 1 @@ -2030,7 +2066,10 @@ private auto executeImpl(alias pipeFunc, Cmd)( size_t maxOutput = size_t.max, in char[] workDir = null) { + import std.string; import std.typecons : Tuple; + import std.array : appender; + import std.algorithm : min; auto p = pipeFunc(commandLine, Redirect.stdout | Redirect.stderrToStdout, env, config, workDir); @@ -2059,6 +2098,7 @@ private auto executeImpl(alias pipeFunc, Cmd)( unittest { + import std.string; // To avoid printing the newline characters, we use the echo|set trick on // Windows, and printf on POSIX (neither echo -n nor echo \c are portable). version (Windows) TestScript prog = @@ -2079,6 +2119,7 @@ unittest unittest { + import std.string; auto r1 = executeShell("echo foo"); assert (r1.status == 0); assert (r1.output.chomp() == "foo"); @@ -2361,6 +2402,7 @@ private string escapeShellCommandString(string command) @safe pure private string escapeWindowsShellCommand(in char[] command) @safe pure { + import std.array : appender; auto result = appender!string(); result.reserve(command.length); @@ -2526,12 +2568,15 @@ version(Windows) version(unittest) { import core.sys.windows.windows; import core.stdc.stddef; + import std.array; extern (Windows) wchar_t** CommandLineToArgvW(wchar_t*, int*); extern (C) size_t wcslen(in wchar *); string[] parseCommandLine(string line) { + import std.algorithm : map; + import std.array : array; LPWSTR lpCommandLine = (to!(wchar[])(line) ~ "\0"w).ptr; int numArgs; LPWSTR* args = CommandLineToArgvW(lpCommandLine, &numArgs); @@ -2617,7 +2662,19 @@ string escapeShellFileName(in char[] fileName) @trusted pure nothrow // preparation - see below. version (Windows) + { + // If a file starts with &, it can cause cmd.exe to misinterpret + // the file name as the stream redirection syntax: + // command > "&foo.txt" + // gets interpreted as + // command >&foo.txt + // Prepend .\ to disambiguate. + + if (fileName.length && fileName[0] == '&') + return cast(string)(`".\` ~ fileName ~ '"'); + return cast(string)('"' ~ fileName ~ '"'); + } else return escapePosixArgument(fileName); } @@ -2641,7 +2698,7 @@ unittest // rdmd --main -unittest -version=unittest_burnin process.d auto helper = absolutePath("std_process_unittest_helper"); - assert(shell(helper ~ " hello").split("\0")[1..$] == ["hello"], "Helper malfunction"); + assert(executeShell(helper ~ " hello").output.split("\0")[1..$] == ["hello"], "Helper malfunction"); void test(string[] s, string fn) { @@ -2650,19 +2707,23 @@ unittest e = escapeShellCommand(helper ~ s); { - scope(failure) writefln("shell() failed.\nExpected:\t%s\nEncoded:\t%s", s, [e]); - g = shell(e).split("\0")[1..$]; + scope(failure) writefln("executeShell() failed.\nExpected:\t%s\nEncoded:\t%s", s, [e]); + auto result = executeShell(e); + assert(result.status == 0, "std_process_unittest_helper failed"); + g = result.output.split("\0")[1..$]; } - assert(s == g, format("shell() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); + assert(s == g, format("executeShell() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); e = escapeShellCommand(helper ~ s) ~ ">" ~ escapeShellFileName(fn); { - scope(failure) writefln("system() failed.\nExpected:\t%s\nFilename:\t%s\nEncoded:\t%s", s, [fn], [e]); - system(e); + scope(failure) writefln("executeShell() with redirect failed.\nExpected:\t%s\nFilename:\t%s\nEncoded:\t%s", s, [fn], [e]); + auto result = executeShell(e); + assert(result.status == 0, "std_process_unittest_helper failed"); + assert(!result.output.length, "No output expected, got:\n" ~ result.output); g = readText(fn).split("\0")[1..$]; } remove(fn); - assert(s == g, format("system() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); + assert(s == g, format("executeShell() with redirect test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); } while (true) @@ -2698,7 +2759,7 @@ unittest } // generate filename - string fn = "test_"; + string fn; foreach (l; 0..uniform(1, 10)) { dchar c; @@ -2721,6 +2782,7 @@ unittest fn ~= c; } + fn = fn[0..$/2] ~ "_testfile_" ~ fn[$/2..$]; test(args, fn); } @@ -2885,6 +2947,7 @@ static: } else version (Windows) { + import std.uni : toUpper; auto envBlock = GetEnvironmentStringsW(); enforce(envBlock, "Failed to retrieve environment variables."); scope(exit) FreeEnvironmentStringsW(envBlock); @@ -2897,6 +2960,13 @@ static: start = i+1; while (envBlock[i] != '\0') ++i; + + // Ignore variables with empty names. These are used internally + // by Windows to keep track of each drive's individual current + // directory. + if (!name.length) + continue; + // Just like in POSIX systems, environment variables may be // defined more than once in an environment block on Windows, // and it is just as much of a security issue there. Moreso, @@ -2986,13 +3056,22 @@ unittest // Wine has some bugs related to environment variables: // - Wine allows the existence of an env. variable with the name // "\0", but GetEnvironmentVariable refuses to retrieve it. + // As of 2.067 we filter these out anyway (see comment in toAA). // - If an env. variable has zero length, i.e. is "\0", // GetEnvironmentVariable should return 1. Instead it returns // 0, indicating the variable doesn't exist. - version (Windows) if (n.length == 0 || v.length == 0) continue; + version (Windows) if (v.length == 0) continue; assert (v == environment[n]); } + + // ... and back again. + foreach (n, v; aa) + environment[n] = v; + + // Complete the roundtrip + auto aa2 = environment.toAA(); + assert(aa == aa2); } @@ -3010,7 +3089,7 @@ Macros: WIKI=Phobos/StdProcess Copyright: Copyright Digital Mars 2007 - 2009. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei Alexandrescu), $(WEB thecybershadow.net, Vladimir Panteleev) @@ -3025,10 +3104,8 @@ Distributed under the Boost Software License, Version 1.0. import core.stdc.stdlib; -import std.c.stdlib; import core.stdc.errno; import core.thread; -import std.c.process; import core.stdc.string; version (Windows) @@ -3058,7 +3135,7 @@ version (unittest) in turn signal an error in command's execution). Note: On Unix systems, the homonym C function (which is accessible - to D programs as $(LINK2 std_c_process.html, std.c._system)) + to D programs as $(LINK2 core_stdc_stdlib.html, core.stdc.stdlib._system)) returns a code in the same format as $(LUCKY waitpid, waitpid), meaning that C programs must use the $(D WEXITSTATUS) macro to extract the actual exit code from the $(D system) call. D's $(D @@ -3068,8 +3145,8 @@ version (unittest) deprecated("Please use wait(spawnShell(command)) or executeShell(command) instead") int system(string command) { - if (!command.ptr) return std.c.process.system(null); - immutable status = std.c.process.system(command.tempCString()); + if (!command.ptr) return core.stdc.stdlib.system(null); + immutable status = core.stdc.stdlib.system(command.tempCString()); if (status == -1) return status; version (Posix) { @@ -3087,6 +3164,7 @@ int system(string command) private void toAStringz(in string[] a, const(char)**az) { + import std.string : toStringz; foreach(string s; a) { *az++ = toStringz(s); @@ -3105,14 +3183,16 @@ private void toAStringz(in string[] a, const(char)**az) // // toAStringz(argv, argv_); // -// return std.c.process.spawnvp(mode, pathname.tempCString(), argv_); +// return spawnvp(mode, pathname.tempCString(), argv_); // } //} // Incorporating idea (for spawnvp() on Posix) from Dave Fladebo -alias P_WAIT = std.c.process._P_WAIT; -alias P_NOWAIT = std.c.process._P_NOWAIT; +enum { _P_WAIT, _P_NOWAIT, _P_OVERLAY }; +version(Windows) extern(C) int spawnvp(int, in char *, in char **); +alias P_WAIT = _P_WAIT; +alias P_NOWAIT = _P_NOWAIT; deprecated("Please use spawnProcess instead") int spawnvp(int mode, string pathname, string[] argv) @@ -3127,7 +3207,7 @@ int spawnvp(int mode, string pathname, string[] argv) } else version (Windows) { - return std.c.process.spawnvp(mode, pathname.tempCString(), argv_); + return spawnvp(mode, pathname.tempCString(), argv_); } else static assert(0, "spawnvp not implemented for this OS."); @@ -3146,7 +3226,7 @@ int _spawnvp(int mode, in char *pathname, in char **argv) if(!pid) { // child - std.c.process.execvp(pathname, argv); + core.sys.posix.unistd.execvp(pathname, argv); goto Lerror; } else if(pid > 0) @@ -3310,13 +3390,22 @@ else else static assert (false, "Unsupported platform"); } +// Move these C declarations to druntime if we decide to keep the D wrappers +extern(C) +{ + int execv(in char *, in char **); + int execve(in char *, in char **, in char **); + int execvp(in char *, in char **); + version(Windows) int execvpe(in char *, in char **, in char **); +} + private int execv_(in string pathname, in string[] argv) { auto argv_ = cast(const(char)**)alloca((char*).sizeof * (1 + argv.length)); toAStringz(argv, argv_); - return std.c.process.execv(pathname.tempCString(), argv_); + return execv(pathname.tempCString(), argv_); } private int execve_(in string pathname, in string[] argv, in string[] envp) @@ -3327,7 +3416,7 @@ private int execve_(in string pathname, in string[] argv, in string[] envp) toAStringz(argv, argv_); toAStringz(envp, envp_); - return std.c.process.execve(pathname.tempCString(), argv_, envp_); + return execve(pathname.tempCString(), argv_, envp_); } private int execvp_(in string pathname, in string[] argv) @@ -3336,13 +3425,14 @@ private int execvp_(in string pathname, in string[] argv) toAStringz(argv, argv_); - return std.c.process.execvp(pathname.tempCString(), argv_); + return execvp(pathname.tempCString(), argv_); } private int execvpe_(in string pathname, in string[] argv, in string[] envp) { version(Posix) { + import std.array : split; // Is pathname rooted? if(pathname[0] == '/') { @@ -3352,7 +3442,7 @@ version(Posix) else { // No, so must traverse PATHs, looking for first match - string[] envPaths = std.array.split( + string[] envPaths = split( to!string(core.stdc.stdlib.getenv("PATH")), ":"); int iRet = 0; @@ -3382,7 +3472,7 @@ else version(Windows) toAStringz(argv, argv_); toAStringz(envp, envp_); - return std.c.process.execvpe(pathname.tempCString(), argv_, envp_); + return execvpe(pathname.tempCString(), argv_, envp_); } else { @@ -3431,6 +3521,7 @@ string shell(string cmd) { version(Windows) { + import std.array : appender; // Generate a random filename auto a = appender!string(); foreach (ref e; 0 .. 8) @@ -3480,7 +3571,7 @@ deprecated unittest /** Gets the value of environment variable $(D name) as a string. Calls -$(LINK2 std_c_stdlib.html#_getenv, std.c.stdlib._getenv) +$(LINK2 core_stdc_stdlib.html#_getenv, core.stdc.stdlib._getenv) internally. $(RED Deprecated. Please use $(LREF environment.opIndex) or @@ -3504,8 +3595,8 @@ string getenv(in char[] name) nothrow Sets the value of environment variable $(D name) to $(D value). If the value was written, or the variable was already present and $(D overwrite) is false, returns normally. Otherwise, it throws an -exception. Calls $(LINK2 std_c_stdlib.html#_setenv, -std.c.stdlib._setenv) internally. +exception. Calls $(LINK2 core_sys_posix_stdlib.html#_setenv, +core.sys.posix.stdlib._setenv) internally. $(RED Deprecated. Please use $(LREF environment.opIndexAssign) instead. This function will be removed in August 2015.) @@ -3516,12 +3607,12 @@ else version(Posix) void setenv(in char[] name, in char[] value, bool overwrite) { errnoEnforce( - std.c.stdlib.setenv(name.tempCString(), value.tempCString(), overwrite) == 0); + core.sys.posix.stdlib.setenv(name.tempCString(), value.tempCString(), overwrite) == 0); } /** Removes variable $(D name) from the environment. Calls $(LINK2 -std_c_stdlib.html#_unsetenv, std.c.stdlib._unsetenv) internally. +core_sys_posix_stdlib.html#_unsetenv, core.sys.posix.stdlib._unsetenv) internally. $(RED Deprecated. Please use $(LREF environment.remove) instead. This function will be removed in August 2015.) @@ -3531,7 +3622,7 @@ else version(Posix) deprecated("Please use environment.remove instead") void unsetenv(in char[] name) { - errnoEnforce(std.c.stdlib.unsetenv(name.tempCString()) == 0); + errnoEnforce(core.sys.posix.stdlib.unsetenv(name.tempCString()) == 0); } version (Posix) deprecated unittest diff --git a/std/random.d b/std/random.d index ac4f4cd9d34..75496aca623 100644 --- a/std/random.d +++ b/std/random.d @@ -31,6 +31,10 @@ distributions, which skew a generator's output statistical distribution in various ways. So far the uniform distribution for integers and real numbers have been implemented. +Upgrading: + $(WEB digitalmars.com/d/1.0/phobos/std_random.html#rand) can + be replaced with $(D uniform!uint()). + Source: $(PHOBOSSRC std/_random.d) Macros: @@ -39,7 +43,7 @@ WIKI = Phobos/StdRandom Copyright: Copyright Andrei Alexandrescu 2008 - 2009, Joseph Rushton Wakeling 2012. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(WEB erdani.org, Andrei Alexandrescu) Masahiro Nakagawa (Xorshift randome generator) $(WEB braingam.es, Joseph Rushton Wakeling) (Algorithm D for random sampling) @@ -56,13 +60,16 @@ Distributed under the Boost Software License, Version 1.0. */ module std.random; -import std.algorithm, std.c.time, std.conv, std.exception, - std.math, std.numeric, std.range, std.traits, - core.thread, core.time; -import std.string : format; -version(unittest) import std.typetuple; +import std.range.primitives; +import std.traits; +version(unittest) +{ + static import std.typetuple; + package alias PseudoRngTypes = std.typetuple.TypeTuple!(MinstdRand0, MinstdRand, Mt19937, Xorshift32, Xorshift64, + Xorshift96, Xorshift128, Xorshift160, Xorshift192); +} // Segments of the code in this file Copyright (c) 1997 by Rick Booth // From "Inner Loops" by Rick Booth, Addison-Wesley @@ -356,6 +363,7 @@ $(D x0). { static if (c == 0) { + import std.exception : enforce; enforce(x0, "Invalid (zero) seed for " ~ LinearCongruentialEngine.stringof); } @@ -455,6 +463,7 @@ alias MinstdRand = LinearCongruentialEngine!(uint, 48271, 0, 2147483647); unittest { + import std.range; static assert(isForwardRange!MinstdRand); static assert(isUniformRNG!MinstdRand); static assert(isUniformRNG!MinstdRand0); @@ -506,7 +515,7 @@ unittest assert(rnd.front == 399268537); // Check .save works - foreach (Type; TypeTuple!(MinstdRand0, MinstdRand)) + foreach (Type; std.typetuple.TypeTuple!(MinstdRand0, MinstdRand)) { auto rnd1 = Type(unpredictableSeed); auto rnd2 = rnd1.save; @@ -622,6 +631,7 @@ Parameters for the generator. mti = n; if(range.empty && j < n) { + import std.format : format; throw new Exception(format("MersenneTwisterEngine.seed: Input range didn't provide enough"~ " elements: Need %s elemnets.", n)); } @@ -731,6 +741,8 @@ alias Mt19937 = MersenneTwisterEngine!(uint, 32, 624, 397, 31, nothrow unittest { + import std.algorithm; + import std.range; static assert(isUniformRNG!Mt19937); static assert(isUniformRNG!(Mt19937, uint)); static assert(isSeedable!Mt19937); @@ -743,6 +755,10 @@ nothrow unittest unittest { + import std.exception; + import std.range; + import std.algorithm; + Mt19937 gen; assertThrown(gen.seed(map!((a) => unpredictableSeed)(repeat(0, 623)))); @@ -770,8 +786,9 @@ unittest unittest { + import std.range; // Check .save works - foreach(Type; TypeTuple!(Mt19937)) + foreach(Type; std.typetuple.TypeTuple!(Mt19937)) { auto gen1 = Type(unpredictableSeed); auto gen2 = gen1.save; @@ -789,7 +806,7 @@ unittest 0x9d2c5680, 15, 0xefc60000, 18); - foreach (R; TypeTuple!(MT!(uint, 32), MT!(ulong, 32), MT!(ulong, 48), MT!(ulong, 64))) + foreach (R; std.typetuple.TypeTuple!(MT!(uint, 32), MT!(ulong, 32), MT!(ulong, 48), MT!(ulong, 64))) auto a = R(); } @@ -1027,6 +1044,7 @@ alias Xorshift = Xorshift128; /// ditto unittest { + import std.range; static assert(isForwardRange!Xorshift); static assert(isUniformRNG!Xorshift); static assert(isUniformRNG!(Xorshift, uint)); @@ -1043,7 +1061,7 @@ unittest [0UL, 246875399, 3690007200, 1264581005, 3906711041, 1866187943, 2481925219, 2464530826, 1604040631, 3653403911] ]; - alias XorshiftTypes = TypeTuple!(Xorshift32, Xorshift64, Xorshift96, Xorshift128, Xorshift160, Xorshift192); + alias XorshiftTypes = std.typetuple.TypeTuple!(Xorshift32, Xorshift64, Xorshift96, Xorshift128, Xorshift160, Xorshift192); foreach (I, Type; XorshiftTypes) { @@ -1085,11 +1103,6 @@ unittest * } * ---- */ -version(unittest) -{ - package alias PseudoRngTypes = TypeTuple!(MinstdRand0, MinstdRand, Mt19937, Xorshift32, Xorshift64, - Xorshift96, Xorshift128, Xorshift160, Xorshift192); -} unittest { @@ -1105,6 +1118,9 @@ A "good" seed for initializing random number engines. Initializing with $(D_PARAM unpredictableSeed) makes engines generate different random number sequences every run. +Returns: +A single unsigned integer seed value, different on each successive call + Example: ---- @@ -1114,8 +1130,9 @@ auto n = rnd.front; ---- */ -@property uint unpredictableSeed() +@property uint unpredictableSeed() @trusted { + import core.thread : Thread, getpid, TickDuration; static bool seeded; static MinstdRand0 rand; if (!seeded) @@ -1128,7 +1145,7 @@ auto n = rnd.front; return cast(uint) (TickDuration.currSystemTick.length ^ rand.front); } -unittest +@safe unittest { // not much to test here auto a = unpredictableSeed; @@ -1157,9 +1174,15 @@ unittest Global random number generator used by various functions in this module whenever no generator is specified. It is allocated per-thread and initialized to an unpredictable value for each thread. + +Returns: +A singleton instance of the default random number generator */ -@property ref Random rndGen() +@property ref Random rndGen() @safe { + import std.algorithm : map; + import std.range : repeat; + static Random result; static bool initialized; if (!initialized) @@ -1181,6 +1204,17 @@ either side). Valid values for $(D boundaries) are $(D "[]"), $(D is closed to the left and open to the right. The version that does not take $(D urng) uses the default generator $(D rndGen). +Params: + a = lower bound of the _uniform distribution + b = upper bound of the _uniform distribution + urng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + A single random variate drawn from the _uniform distribution + between $(D a) and $(D b), whose type is the common type of + these parameters + Example: ---- @@ -1197,7 +1231,7 @@ auto uniform(string boundaries = "[)", T1, T2) return uniform!(boundaries, T1, T2, Random)(a, b, rndGen); } -unittest +@safe unittest { MinstdRand0 gen; foreach (i; 0 .. 20) @@ -1233,9 +1267,12 @@ auto uniform(string boundaries = "[)", (T1 a, T2 b, ref UniformRandomNumberGenerator urng) if (isFloatingPoint!(CommonType!(T1, T2)) && isUniformRNG!UniformRandomNumberGenerator) { + import std.exception : enforce; + import std.conv : text; alias NumberType = Unqual!(CommonType!(T1, T2)); static if (boundaries[0] == '(') { + import std.math : nextafter; NumberType _a = nextafter(cast(NumberType) a, NumberType.infinity); } else @@ -1244,6 +1281,7 @@ if (isFloatingPoint!(CommonType!(T1, T2)) && isUniformRNG!UniformRandomNumberGen } static if (boundaries[1] == ')') { + import std.math : nextafter; NumberType _b = nextafter(cast(NumberType) b, -NumberType.infinity); } else @@ -1328,6 +1366,8 @@ auto uniform(string boundaries = "[)", T1, T2, RandomGen) if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) && isUniformRNG!RandomGen) { + import std.exception : enforce; + import std.conv : text, unsigned; alias ResultType = Unqual!(CommonType!(T1, T2)); static if (boundaries[0] == '(') { @@ -1383,8 +1423,9 @@ if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) && return cast(ResultType)(lower + offset); } -unittest +@safe unittest { + import std.conv : to; auto gen = Mt19937(unpredictableSeed); static assert(isForwardRange!(typeof(gen))); @@ -1395,7 +1436,7 @@ unittest auto c = uniform(0.0, 1.0); assert(0 <= c && c < 1); - foreach (T; TypeTuple!(char, wchar, dchar, byte, ubyte, short, ushort, + foreach (T; std.typetuple.TypeTuple!(char, wchar, dchar, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real)) { T lo = 0, hi = 100; @@ -1449,7 +1490,7 @@ unittest auto reproRng = Xorshift(239842); - foreach (T; TypeTuple!(char, wchar, dchar, byte, ubyte, short, + foreach (T; std.typetuple.TypeTuple!(char, wchar, dchar, byte, ubyte, short, ushort, int, uint, long, ulong)) { T lo = T.min + 10, hi = T.max - 10; @@ -1519,8 +1560,16 @@ unittest /** Generates a uniformly-distributed number in the range $(D [T.min, -T.max]) for any integral type $(D T). If no random number generator is -passed, uses the default $(D rndGen). +T.max]) for any integral or character type $(D T). If no random +number generator is passed, uses the default $(D rndGen). + +Params: + urng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Random variate drawn from the _uniform distribution across all + possible values of the integral or character type $(D T). */ auto uniform(T, UniformRandomNumberGenerator) (ref UniformRandomNumberGenerator urng) @@ -1558,9 +1607,9 @@ if (!is(T == enum) && (isIntegral!T || isSomeChar!T)) return uniform!T(rndGen); } -unittest +@safe unittest { - foreach(T; TypeTuple!(char, wchar, dchar, byte, ubyte, short, ushort, + foreach(T; std.typetuple.TypeTuple!(char, wchar, dchar, byte, ubyte, short, ushort, int, uint, long, ulong)) { T init = uniform!T(); @@ -1581,6 +1630,14 @@ unittest /** Returns a uniformly selected member of enum $(D E). If no random number generator is passed, uses the default $(D rndGen). + +Params: + urng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Random variate drawn with equal probability from any + of the possible values of the enum $(D E). */ auto uniform(E, UniformRandomNumberGenerator) (ref UniformRandomNumberGenerator urng) @@ -1598,13 +1655,13 @@ if (is(E == enum)) } /// -unittest +@safe unittest { enum Fruit { apple, mango, pear } auto randFruit = uniform!Fruit(); } -unittest +@safe unittest { enum Fruit { Apple = 12, Mango = 29, Pear = 72 } foreach (_; 0 .. 100) @@ -1625,6 +1682,15 @@ unittest * $(D uniform01) offers a faster generation of random variates than * the equivalent $(D uniform!"[$(RPAREN)"(0.0, 1.0)) and so may be preferred * for some applications. + * + * Params: + * urng = (optional) random number generator to use; + * if not specified, defaults to $(D rndGen) + * + * Returns: + * Floating-point random variate of type $(D T) drawn from the _uniform + * distribution across the half-open interval [0, 1$(RPAREN). + * */ T uniform01(T = double)() if (isFloatingPoint!T) @@ -1684,14 +1750,14 @@ body assert(false); } -unittest +@safe unittest { import std.typetuple; foreach (UniformRNG; PseudoRngTypes) { - foreach (T; TypeTuple!(float, double, real)) - { + foreach (T; std.typetuple.TypeTuple!(float, double, real)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 UniformRNG rng = UniformRNG(unpredictableSeed); auto a = uniform01(); @@ -1715,7 +1781,7 @@ unittest while (--i && uniform01!T(rng) == init) {} assert(i > 0); assert(i < 50); - } + }(); } } @@ -1727,6 +1793,7 @@ $(D 1). If $(D useThis) is provided, it is used as storage. F[] uniformDistribution(F = double)(size_t n, F[] useThis = null) if(isFloatingPoint!F) { + import std.numeric : normalize; useThis.length = n; foreach (ref e; useThis) { @@ -1736,21 +1803,28 @@ F[] uniformDistribution(F = double)(size_t n, F[] useThis = null) return useThis; } -unittest +@safe unittest { + import std.math; + import std.algorithm; static assert(is(CommonType!(double, int) == double)); auto a = uniformDistribution(5); - enforce(a.length == 5); - enforce(approxEqual(reduce!"a + b"(a), 1)); + assert(a.length == 5); + assert(approxEqual(reduce!"a + b"(a), 1)); a = uniformDistribution(10, a); - enforce(a.length == 10); - enforce(approxEqual(reduce!"a + b"(a), 1)); + assert(a.length == 10); + assert(approxEqual(reduce!"a + b"(a), 1)); } /** Shuffles elements of $(D r) using $(D gen) as a shuffler. $(D r) must be a random-access range with length. If no RNG is specified, $(D rndGen) will be used. + +Params: + r = random-access range whose elements are to be shuffled + gen = (optional) random number generator to use; if not + specified, defaults to $(D rndGen) */ void randomShuffle(Range, RandomGen)(Range r, ref RandomGen gen) @@ -1768,6 +1842,7 @@ void randomShuffle(Range)(Range r) unittest { + import std.algorithm; foreach(RandomGen; PseudoRngTypes) { // Also tests partialShuffle indirectly. @@ -1775,9 +1850,11 @@ unittest auto b = a.dup; auto gen = RandomGen(unpredictableSeed); randomShuffle(a, gen); - assert(a.sort == b); + sort(a); + assert(a == b); randomShuffle(a); - assert(a.sort == b); + sort(a); + assert(a == b); } } @@ -1791,10 +1868,19 @@ $(D partialShuffle) was called. $(D r) must be a random-access range with length. $(D n) must be less than or equal to $(D r.length). If no RNG is specified, $(D rndGen) will be used. + +Params: + r = random-access range whose elements are to be shuffled + n = number of elements of $(D r) to shuffle (counting from the beginning); + must be less than $(D r.length) + gen = (optional) random number generator to use; if not + specified, defaults to $(D rndGen) */ void partialShuffle(Range, RandomGen)(Range r, in size_t n, ref RandomGen gen) if(isRandomAccessRange!Range && isUniformRNG!RandomGen) { + import std.exception : enforce; + import std.algorithm : swapAt; enforce(n <= r.length, "n must be <= r.length for partialShuffle."); foreach (i; 0 .. n) { @@ -1811,6 +1897,7 @@ void partialShuffle(Range)(Range r, in size_t n) unittest { + import std.algorithm; foreach(RandomGen; PseudoRngTypes) { auto a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; @@ -1818,10 +1905,12 @@ unittest auto gen = RandomGen(unpredictableSeed); partialShuffle(a, 5, gen); assert(a[5 .. $] == b[5 .. $]); - assert(a[0 .. 5].sort == b[0 .. 5]); + sort(a[0 .. 5]); + assert(a[0 .. 5] == b[0 .. 5]); partialShuffle(a, 6); assert(a[6 .. $] == b[6 .. $]); - assert(a[0 .. 6].sort == b[0 .. 6]); + sort(a[0 .. 6]); + assert(a[0 .. 6] == b[0 .. 6]); } } @@ -1829,6 +1918,20 @@ unittest Rolls a dice with relative probabilities stored in $(D proportions). Returns the index in $(D proportions) that was chosen. +Params: + rnd = (optional) random number generator to use; if not + specified, defaults to $(D rndGen) + proportions = forward range or list of individual values + whose elements correspond to the probabilities + with which to choose the corresponding index + value + +Returns: + Random variate drawn from the index values + [0, ... $(D proportions.length) - 1], with the probability + of getting an individual index value $(D i) being proportional to + $(D proportions[i]). + Example: ---- @@ -1866,9 +1969,17 @@ if (isNumeric!Num) } private size_t diceImpl(Rng, Range)(ref Rng rng, Range proportions) -if (isForwardRange!Range && isNumeric!(ElementType!Range) && isForwardRange!Rng) + if (isForwardRange!Range && isNumeric!(ElementType!Range) && isForwardRange!Rng) +in { - double sum = reduce!((a,b) { assert(b >= 0); return a + b; })(0.0, proportions.save); + import std.algorithm : all; + assert(proportions.save.all!"a >= 0"); +} +body +{ + import std.exception : enforce; + import std.algorithm : reduce; + double sum = reduce!"a + b"(0.0, proportions.save); enforce(sum > 0, "Proportions in a dice cannot sum to zero"); immutable point = uniform(0.0, sum, rng); assert(point < sum); @@ -1905,6 +2016,16 @@ must be a random-access range with length. If no random number generator is passed to $(D randomCover), the thread-global RNG rndGen will be used internally. +Params: + r = random-access range to cover + rng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Range whose elements consist of the elements of $(D r), + in random order. Will be a forward range if both $(D r) and + $(D rng) are forward ranges, an input range otherwise. + Example: ---- int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; @@ -1955,7 +2076,10 @@ struct RandomCover(Range, UniformRNG = void) { _input = input; _chosen.length = _input.length; - _alreadyChosen = 0; + if (_chosen.length == 0) + { + _alreadyChosen = 1; + } } } else @@ -1967,7 +2091,10 @@ struct RandomCover(Range, UniformRNG = void) _input = input; _rng = rng; _chosen.length = _input.length; - _alreadyChosen = 0; + if (_chosen.length == 0) + { + _alreadyChosen = 1; + } } this(Range input, UniformRNG rng) @@ -1995,7 +2122,6 @@ struct RandomCover(Range, UniformRNG = void) { if (_alreadyChosen == 0) { - _chosen[] = false; popFront(); } return _input[_current]; @@ -2066,8 +2192,10 @@ auto randomCover(Range)(Range r) unittest { + import std.algorithm; + import std.conv; int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; - foreach (UniformRNG; TypeTuple!(void, PseudoRngTypes)) + foreach (UniformRNG; std.typetuple.TypeTuple!(void, PseudoRngTypes)) { static if (is(UniformRNG == void)) { @@ -2097,6 +2225,15 @@ unittest } } +unittest +{ + // Bugzilla 12589 + int[] r = []; + auto rc = randomCover(r); + assert(rc.length == 0); + assert(rc.empty); +} + // RandomSample /** Selects a random subsample out of $(D r), containing exactly $(D n) @@ -2105,6 +2242,28 @@ range. The total length of $(D r) must be known. If $(D total) is passed in, the total number of sample is considered to be $(D total). Otherwise, $(D RandomSample) uses $(D r.length). +Params: + r = range to sample from + n = number of elements to include in the sample; + must be less than or equal to the total number + of elements in $(D r) and/or the parameter + $(D total) (if provided) + total = (semi-optional) number of elements of $(D r) + from which to select the sample (counting from + the beginning); must be less than or equal to + the total number of elements in $(D r) itself. + May be omitted if $(D r) has the $(D .length) + property and the sample is to be drawn from + all elements of $(D r). + rng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Range whose elements consist of a randomly selected subset of + the elements of $(D r), in the same order as these elements + appear in $(D r) itself. Will be a forward range if both $(D r) + and $(D rng) are forward ranges, an input range otherwise. + $(D RandomSample) implements Jeffrey Scott Vitter's Algorithm D (see Vitter $(WEB dx.doi.org/10.1145/358105.893, 1984), $(WEB dx.doi.org/10.1145/23002.23003, 1987)), which selects a sample @@ -2225,6 +2384,8 @@ struct RandomSample(Range, UniformRNG = void) private void initialize(size_t howMany, size_t total) { + import std.exception : enforce; + import std.conv : text; _available = total; _toSelect = howMany; enforce(_toSelect <= _available, @@ -2428,6 +2589,7 @@ Variable names are chosen to match those in Vitter's paper. */ private size_t skipD() { + import std.math : isNaN, trunc; // Confirm that the check in Step D1 is valid and we // haven't been sent here by mistake assert((_alphaInverse * _toSelect) <= _available); @@ -2439,7 +2601,7 @@ Variable names are chosen to match those in Vitter's paper. size_t qu1 = 1 + _available - _toSelect; double x, y1; - assert(!_Vprime.isNaN); + assert(!_Vprime.isNaN()); while (true) { @@ -2572,6 +2734,9 @@ auto randomSample(Range, UniformRNG)(Range r, size_t n, auto ref UniformRNG rng) unittest { + import std.exception; + import std.range; + import std.conv : text; // For test purposes, an infinite input range struct TestInputRange { diff --git a/std/range/interfaces.d b/std/range/interfaces.d new file mode 100644 index 00000000000..f7ad377bdbf --- /dev/null +++ b/std/range/interfaces.d @@ -0,0 +1,508 @@ +/** +This module is a submodule of $(LINK2 std_range_package.html, std.range). + +The main $(D std.range) module provides template-based tools for working with +ranges, but sometimes an object-based interface for ranges is needed, such as +when runtime polymorphism is required. For this purpose, this submodule +provides a number of object and $(D interface) definitions that can be used to +wrap around _range objects created by the $(D std.range) templates. + +$(BOOKTABLE , + $(TR $(TD $(D $(LREF InputRange))) + $(TD Wrapper for input ranges. + )) + $(TR $(TD $(D $(LREF InputAssignable))) + $(TD Wrapper for input ranges with assignable elements. + )) + $(TR $(TD $(D $(LREF ForwardRange))) + $(TD Wrapper for forward ranges. + )) + $(TR $(TD $(D $(LREF ForwardAssignable))) + $(TD Wrapper for forward ranges with assignable elements. + )) + $(TR $(TD $(D $(LREF BidirectionalRange))) + $(TD Wrapper for bidirectional ranges. + )) + $(TR $(TD $(D $(LREF BidirectionalAssignable))) + $(TD Wrapper for bidirectional ranges with assignable elements. + )) + $(TR $(TD $(D $(LREF RandomAccessFinite))) + $(TD Wrapper for finite random-access ranges. + )) + $(TR $(TD $(D $(LREF RandomAccessAssignable))) + $(TD Wrapper for finite random-access ranges with assignable elements. + )) + $(TR $(TD $(D $(LREF RandomAccessInfinite))) + $(TD Wrapper for infinite random-access ranges. + )) + $(TR $(TD $(D $(LREF OutputRange))) + $(TD Wrapper for output ranges. + )) + $(TR $(TD $(D $(LREF OutputRangeObject))) + $(TD Class that implements the $(D OutputRange) interface and wraps the + $(D put) methods in virtual functions. + )) + $(TR $(TD $(D $(LREF InputRangeObject))) + $(TD Class that implements the $(D InputRange) interface and wraps the + input _range methods in virtual functions. + )) + $(TR $(TD $(D $(LREF RefRange))) + $(TD Wrapper around a forward _range that gives it reference semantics. + )) +) + + +Source: $(PHOBOSSRC std/range/_interfaces.d) + +Macros: + +WIKI = Phobos/StdRange + +Copyright: Copyright by authors 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha, +and Jonathan M Davis. Credit for some of the ideas in building this module goes +to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi). +*/ +module std.range.interfaces; + +import std.range.primitives; +import std.traits; +import std.typetuple; + +/**These interfaces are intended to provide virtual function-based wrappers + * around input ranges with element type E. This is useful where a well-defined + * binary interface is required, such as when a DLL function or virtual function + * needs to accept a generic range as a parameter. Note that + * $(LREF isInputRange) and friends check for conformance to structural + * interfaces, not for implementation of these $(D interface) types. + * + * Limitations: + * + * These interfaces are not capable of forwarding $(D ref) access to elements. + * + * Infiniteness of the wrapped range is not propagated. + * + * Length is not propagated in the case of non-random access ranges. + * + * See_Also: + * $(LREF inputRangeObject) + */ +interface InputRange(E) { + /// + @property E front(); + + /// + E moveFront(); + + /// + void popFront(); + + /// + @property bool empty(); + + /* Measurements of the benefits of using opApply instead of range primitives + * for foreach, using timings for iterating over an iota(100_000_000) range + * with an empty loop body, using the same hardware in each case: + * + * Bare Iota struct, range primitives: 278 milliseconds + * InputRangeObject, opApply: 436 milliseconds (1.57x penalty) + * InputRangeObject, range primitives: 877 milliseconds (3.15x penalty) + */ + + /**$(D foreach) iteration uses opApply, since one delegate call per loop + * iteration is faster than three virtual function calls. + */ + int opApply(int delegate(E)); + + /// Ditto + int opApply(int delegate(size_t, E)); + +} + +/// +unittest +{ + import std.algorithm : map, iota; + + void useRange(InputRange!int range) { + // Function body. + } + + // Create a range type. + auto squares = map!"a * a"(iota(10)); + + // Wrap it in an interface. + auto squaresWrapped = inputRangeObject(squares); + + // Use it. + useRange(squaresWrapped); +} + +/**Interface for a forward range of type $(D E).*/ +interface ForwardRange(E) : InputRange!E { + /// + @property ForwardRange!E save(); +} + +/**Interface for a bidirectional range of type $(D E).*/ +interface BidirectionalRange(E) : ForwardRange!(E) { + /// + @property BidirectionalRange!E save(); + + /// + @property E back(); + + /// + E moveBack(); + + /// + void popBack(); +} + +/**Interface for a finite random access range of type $(D E).*/ +interface RandomAccessFinite(E) : BidirectionalRange!(E) { + /// + @property RandomAccessFinite!E save(); + + /// + E opIndex(size_t); + + /// + E moveAt(size_t); + + /// + @property size_t length(); + + /// + alias opDollar = length; + + // Can't support slicing until issues with requiring slicing for all + // finite random access ranges are fully resolved. + version(none) { + /// + RandomAccessFinite!E opSlice(size_t, size_t); + } +} + +/**Interface for an infinite random access range of type $(D E).*/ +interface RandomAccessInfinite(E) : ForwardRange!E { + /// + E moveAt(size_t); + + /// + @property RandomAccessInfinite!E save(); + + /// + E opIndex(size_t); +} + +/**Adds assignable elements to InputRange.*/ +interface InputAssignable(E) : InputRange!E { + /// + @property void front(E newVal); +} + +/**Adds assignable elements to ForwardRange.*/ +interface ForwardAssignable(E) : InputAssignable!E, ForwardRange!E { + /// + @property ForwardAssignable!E save(); +} + +/**Adds assignable elements to BidirectionalRange.*/ +interface BidirectionalAssignable(E) : ForwardAssignable!E, BidirectionalRange!E { + /// + @property BidirectionalAssignable!E save(); + + /// + @property void back(E newVal); +} + +/**Adds assignable elements to RandomAccessFinite.*/ +interface RandomFiniteAssignable(E) : RandomAccessFinite!E, BidirectionalAssignable!E { + /// + @property RandomFiniteAssignable!E save(); + + /// + void opIndexAssign(E val, size_t index); +} + +/**Interface for an output range of type $(D E). Usage is similar to the + * $(D InputRange) interface and descendants.*/ +interface OutputRange(E) { + /// + void put(E); +} + +@safe unittest +{ + // 6973 + static assert(isOutputRange!(OutputRange!int, int)); +} + + +// CTFE function that generates mixin code for one put() method for each +// type E. +private string putMethods(E...)() +{ + import std.conv : to; + + string ret; + + foreach (ti, Unused; E) + { + ret ~= "void put(E[" ~ to!string(ti) ~ "] e) { .put(_range, e); }"; + } + + return ret; +} + +/**Implements the $(D OutputRange) interface for all types E and wraps the + * $(D put) method for each type $(D E) in a virtual function. + */ +class OutputRangeObject(R, E...) : staticMap!(OutputRange, E) { + // @BUG 4689: There should be constraints on this template class, but + // DMD won't let me put them in. + private R _range; + + this(R range) { + this._range = range; + } + + mixin(putMethods!E()); +} + + +/**Returns the interface type that best matches $(D R).*/ +template MostDerivedInputRange(R) if (isInputRange!(Unqual!R)) { + private alias E = ElementType!R; + + static if (isRandomAccessRange!R) { + static if (isInfinite!R) { + alias MostDerivedInputRange = RandomAccessInfinite!E; + } else static if (hasAssignableElements!R) { + alias MostDerivedInputRange = RandomFiniteAssignable!E; + } else { + alias MostDerivedInputRange = RandomAccessFinite!E; + } + } else static if (isBidirectionalRange!R) { + static if (hasAssignableElements!R) { + alias MostDerivedInputRange = BidirectionalAssignable!E; + } else { + alias MostDerivedInputRange = BidirectionalRange!E; + } + } else static if (isForwardRange!R) { + static if (hasAssignableElements!R) { + alias MostDerivedInputRange = ForwardAssignable!E; + } else { + alias MostDerivedInputRange = ForwardRange!E; + } + } else { + static if (hasAssignableElements!R) { + alias MostDerivedInputRange = InputAssignable!E; + } else { + alias MostDerivedInputRange = InputRange!E; + } + } +} + +/**Implements the most derived interface that $(D R) works with and wraps + * all relevant range primitives in virtual functions. If $(D R) is already + * derived from the $(D InputRange) interface, aliases itself away. + */ +template InputRangeObject(R) if (isInputRange!(Unqual!R)) { + static if (is(R : InputRange!(ElementType!R))) { + alias InputRangeObject = R; + } else static if (!is(Unqual!R == R)) { + alias InputRangeObject = InputRangeObject!(Unqual!R); + } else { + + /// + class InputRangeObject : MostDerivedInputRange!(R) { + private R _range; + private alias E = ElementType!R; + + this(R range) { + this._range = range; + } + + @property E front() { return _range.front; } + + E moveFront() { + return .moveFront(_range); + } + + void popFront() { _range.popFront(); } + @property bool empty() { return _range.empty; } + + static if (isForwardRange!R) { + @property typeof(this) save() { + return new typeof(this)(_range.save); + } + } + + static if (hasAssignableElements!R) { + @property void front(E newVal) { + _range.front = newVal; + } + } + + static if (isBidirectionalRange!R) { + @property E back() { return _range.back; } + + E moveBack() { + return .moveBack(_range); + } + + void popBack() { return _range.popBack(); } + + static if (hasAssignableElements!R) { + @property void back(E newVal) { + _range.back = newVal; + } + } + } + + static if (isRandomAccessRange!R) { + E opIndex(size_t index) { + return _range[index]; + } + + E moveAt(size_t index) { + return .moveAt(_range, index); + } + + static if (hasAssignableElements!R) { + void opIndexAssign(E val, size_t index) { + _range[index] = val; + } + } + + static if (!isInfinite!R) { + @property size_t length() { + return _range.length; + } + + alias opDollar = length; + + // Can't support slicing until all the issues with + // requiring slicing support for finite random access + // ranges are resolved. + version(none) { + typeof(this) opSlice(size_t lower, size_t upper) { + return new typeof(this)(_range[lower..upper]); + } + } + } + } + + // Optimization: One delegate call is faster than three virtual + // function calls. Use opApply for foreach syntax. + int opApply(int delegate(E) dg) { + int res; + + for(auto r = _range; !r.empty; r.popFront()) { + res = dg(r.front); + if (res) break; + } + + return res; + } + + int opApply(int delegate(size_t, E) dg) { + int res; + + size_t i = 0; + for(auto r = _range; !r.empty; r.popFront()) { + res = dg(i, r.front); + if (res) break; + i++; + } + + return res; + } + } + } +} + +/**Convenience function for creating an $(D InputRangeObject) of the proper type. + * See $(LREF InputRange) for an example. + */ +InputRangeObject!R inputRangeObject(R)(R range) if (isInputRange!R) { + static if (is(R : InputRange!(ElementType!R))) { + return range; + } else { + return new InputRangeObject!R(range); + } +} + +/**Convenience function for creating an $(D OutputRangeObject) with a base range + * of type $(D R) that accepts types $(D E). +*/ +template outputRangeObject(E...) { + + /// + OutputRangeObject!(R, E) outputRangeObject(R)(R range) { + return new OutputRangeObject!(R, E)(range); + } +} + +/// +unittest +{ + import std.array; + auto app = appender!(uint[])(); + auto appWrapped = outputRangeObject!(uint, uint[])(app); + static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); + static assert(is(typeof(appWrapped) : OutputRange!(uint))); +} + +unittest +{ + import std.internal.test.dummyrange; + import std.algorithm : equal; + import std.array; + + static void testEquality(R)(iInputRange r1, R r2) { + assert(equal(r1, r2)); + } + + auto arr = [1,2,3,4]; + RandomFiniteAssignable!int arrWrapped = inputRangeObject(arr); + static assert(isRandomAccessRange!(typeof(arrWrapped))); + // static assert(hasSlicing!(typeof(arrWrapped))); + static assert(hasLength!(typeof(arrWrapped))); + arrWrapped[0] = 0; + assert(arr[0] == 0); + assert(arr.moveFront() == 0); + assert(arr.moveBack() == 4); + assert(arr.moveAt(1) == 2); + + foreach(elem; arrWrapped) {} + foreach(i, elem; arrWrapped) {} + + assert(inputRangeObject(arrWrapped) is arrWrapped); + + foreach(DummyType; AllDummyRanges) { + auto d = DummyType.init; + static assert(propagatesRangeType!(DummyType, + typeof(inputRangeObject(d)))); + static assert(propagatesRangeType!(DummyType, + MostDerivedInputRange!DummyType)); + InputRange!uint wrapped = inputRangeObject(d); + assert(equal(wrapped, d)); + } + + // Test output range stuff. + auto app = appender!(uint[])(); + auto appWrapped = outputRangeObject!(uint, uint[])(app); + static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); + static assert(is(typeof(appWrapped) : OutputRange!(uint))); + + appWrapped.put(1); + appWrapped.put([2, 3]); + assert(app.data.length == 3); + assert(equal(app.data, [1,2,3])); +} diff --git a/std/range.d b/std/range/package.d similarity index 72% rename from std/range.d rename to std/range/package.d index 6b45f6c7caf..db9fc9ea557 100644 --- a/std/range.d +++ b/std/range/package.d @@ -14,1946 +14,165 @@ motivation behind them, see Andrei Alexandrescu's article $(LINK2 http://www.informit.com/articles/printerfriendly.aspx?p=1407357&rll=1, $(I On Iteration)). -This module defines several templates for testing whether a given object is a -_range, and what kind of _range it is: -$(BOOKTABLE , - $(TR $(TD $(D $(LREF isInputRange))) - $(TD Tests if something is an $(I input _range), defined to be - something from which one can sequentially read data using the - primitives $(D front), $(D popFront), and $(D empty). - )) - $(TR $(TD $(D $(LREF isOutputRange))) - $(TD Tests if something is an $(I output _range), defined to be - something to which one can sequentially write data using the - $(D $(LREF put)) primitive. - )) - $(TR $(TD $(D $(LREF isForwardRange))) - $(TD Tests if something is a $(I forward _range), defined to be an - input _range with the additional capability that one can save one's - current position with the $(D save) primitive, thus allowing one to - iterate over the same _range multiple times. - )) - $(TR $(TD $(D $(LREF isBidirectionalRange))) - $(TD Tests if something is a $(I bidirectional _range), that is, a - forward _range that allows reverse traversal using the primitives $(D - back) and $(D popBack). - )) - $(TR $(TD $(D $(LREF isRandomAccessRange))) - $(TD Tests if something is a $(I random access _range), which is a - bidirectional _range that also supports the array subscripting - operation via the primitive $(D opIndex). - )) -) - -A number of templates are provided that test for various _range capabilities: - -$(BOOKTABLE , - $(TR $(TD $(D $(LREF hasMobileElements))) - $(TD Tests if a given _range's elements can be moved around using the - primitives $(D moveFront), $(D moveBack), or $(D moveAt). - )) - $(TR $(TD $(D $(LREF ElementType))) - $(TD Returns the element type of a given _range. - )) - $(TR $(TD $(D $(LREF ElementEncodingType))) - $(TD Returns the encoding element type of a given _range. - )) - $(TR $(TD $(D $(LREF hasSwappableElements))) - $(TD Tests if a _range is a forward _range with swappable elements. - )) - $(TR $(TD $(D $(LREF hasAssignableElements))) - $(TD Tests if a _range is a forward _range with mutable elements. - )) - $(TR $(TD $(D $(LREF hasLvalueElements))) - $(TD Tests if a _range is a forward _range with elements that can be - passed by reference and have their address taken. - )) - $(TR $(TD $(D $(LREF hasLength))) - $(TD Tests if a given _range has the $(D length) attribute. - )) - $(TR $(TD $(D $(LREF isInfinite))) - $(TD Tests if a given _range is an $(I infinite _range). - )) - $(TR $(TD $(D $(LREF hasSlicing))) - $(TD Tests if a given _range supports the array slicing operation $(D - R[x..y]). - )) - $(TR $(TD $(D $(LREF walkLength))) - $(TD Computes the length of any _range in O(n) time. - )) -) - -A rich set of _range creation and composition templates are provided that let -you construct new ranges out of existing ranges: - -$(BOOKTABLE , - $(TR $(TD $(D $(LREF retro))) - $(TD Iterates a bidirectional _range backwards. - )) - $(TR $(TD $(D $(LREF stride))) - $(TD Iterates a _range with stride $(I n). - )) - $(TR $(TD $(D $(LREF chain))) - $(TD Concatenates several ranges into a single _range. - )) - $(TR $(TD $(D $(LREF roundRobin))) - $(TD Given $(I n) ranges, creates a new _range that return the $(I n) - first elements of each _range, in turn, then the second element of each - _range, and so on, in a round-robin fashion. - )) - $(TR $(TD $(D $(LREF radial))) - $(TD Given a random-access _range and a starting point, creates a - _range that alternately returns the next left and next right element to - the starting point. - )) - $(TR $(TD $(D $(LREF take))) - $(TD Creates a sub-_range consisting of only up to the first $(I n) - elements of the given _range. - )) - $(TR $(TD $(D $(LREF takeExactly))) - $(TD Like $(D take), but assumes the given _range actually has $(I n) - elements, and therefore also defines the $(D length) property. - )) - $(TR $(TD $(D $(LREF takeOne))) - $(TD Creates a random-access _range consisting of exactly the first - element of the given _range. - )) - $(TR $(TD $(D $(LREF takeNone))) - $(TD Creates a random-access _range consisting of zero elements of the - given _range. - )) - $(TR $(TD $(D $(LREF drop))) - $(TD Creates the _range that results from discarding the first $(I n) - elements from the given _range. - )) - $(TR $(TD $(D $(LREF dropExactly))) - $(TD Creates the _range that results from discarding exactly $(I n) - of the first elements from the given _range. - )) - $(TR $(TD $(D $(LREF dropOne))) - $(TD Creates the _range that results from discarding - the first elements from the given _range. - )) - $(TR $(TD $(D $(LREF repeat))) - $(TD Creates a _range that consists of a single element repeated $(I n) - times, or an infinite _range repeating that element indefinitely. - )) - $(TR $(TD $(D $(LREF cycle))) - $(TD Creates an infinite _range that repeats the given forward _range - indefinitely. Good for implementing circular buffers. - )) - $(TR $(TD $(D $(LREF zip))) - $(TD Given $(I n) _ranges, creates a _range that successively returns a - tuple of all the first elements, a tuple of all the second elements, - etc. - )) - $(TR $(TD $(D $(LREF lockstep))) - $(TD Iterates $(I n) _ranges in lockstep, for use in a $(D foreach) - loop. Similar to $(D zip), except that $(D lockstep) is designed - especially for $(D foreach) loops. - )) - $(TR $(TD $(D $(LREF recurrence))) - $(TD Creates a forward _range whose values are defined by a - mathematical recurrence relation. - )) - $(TR $(TD $(D $(LREF sequence))) - $(TD Similar to $(D recurrence), except that a random-access _range is - created. - )) - $(TR $(TD $(D $(LREF iota))) - $(TD Creates a _range consisting of numbers between a starting point - and ending point, spaced apart by a given interval. - )) - $(TR $(TD $(D $(LREF frontTransversal))) - $(TD Creates a _range that iterates over the first elements of the - given ranges. - )) - $(TR $(TD $(D $(LREF transversal))) - $(TD Creates a _range that iterates over the $(I n)'th elements of the - given random-access ranges. - )) - $(TR $(TD $(D $(LREF transposed))) - $(TD Transposes a _range of ranges. - )) - $(TR $(TD $(D $(LREF indexed))) - $(TD Creates a _range that offers a view of a given _range as though - its elements were reordered according to a given _range of indices. - )) - $(TR $(TD $(D $(LREF chunks))) - $(TD Creates a _range that returns fixed-size chunks of the original - _range. - )) - $(TR $(TD $(D $(LREF only))) - $(TD Creates a _range that iterates over the given arguments. - )) - $(TR $(TD $(D $(LREF tee))) - $(TD Creates a _range that wraps a given _range, forwarding along - its elements while also calling a provided function with each element. - )) -) - -These _range-construction tools are implemented using templates; but sometimes -an object-based interface for ranges is needed. For this purpose, this module -provides a number of object and $(D interface) definitions that can be used to -wrap around _range objects created by the above templates. - -$(BOOKTABLE , - $(TR $(TD $(D $(LREF InputRange))) - $(TD Wrapper for input ranges. - )) - $(TR $(TD $(D $(LREF InputAssignable))) - $(TD Wrapper for input ranges with assignable elements. - )) - $(TR $(TD $(D $(LREF ForwardRange))) - $(TD Wrapper for forward ranges. - )) - $(TR $(TD $(D $(LREF ForwardAssignable))) - $(TD Wrapper for forward ranges with assignable elements. - )) - $(TR $(TD $(D $(LREF BidirectionalRange))) - $(TD Wrapper for bidirectional ranges. - )) - $(TR $(TD $(D $(LREF BidirectionalAssignable))) - $(TD Wrapper for bidirectional ranges with assignable elements. - )) - $(TR $(TD $(D $(LREF RandomAccessFinite))) - $(TD Wrapper for finite random-access ranges. - )) - $(TR $(TD $(D $(LREF RandomAccessAssignable))) - $(TD Wrapper for finite random-access ranges with assignable elements. - )) - $(TR $(TD $(D $(LREF RandomAccessInfinite))) - $(TD Wrapper for infinite random-access ranges. - )) - $(TR $(TD $(D $(LREF OutputRange))) - $(TD Wrapper for output ranges. - )) - $(TR $(TD $(D $(LREF OutputRangeObject))) - $(TD Class that implements the $(D OutputRange) interface and wraps the - $(D put) methods in virtual functions. - )) - $(TR $(TD $(D $(LREF InputRangeObject))) - $(TD Class that implements the $(D InputRange) interface and wraps the - input _range methods in virtual functions. - )) - $(TR $(TD $(D $(LREF RefRange))) - $(TD Wrapper around a forward _range that gives it reference semantics. - )) -) - -Ranges whose elements are sorted afford better efficiency with certain -operations. For this, the $(D $(LREF assumeSorted)) function can be used to -construct a $(D $(LREF SortedRange)) from a pre-sorted _range. The $(LINK2 -std_algorithm.html#sort, $(D std.algorithm.sort)) function also conveniently -returns a $(D SortedRange). $(D SortedRange) objects provide some additional -_range operations that take advantage of the fact that the _range is sorted. - -Finally, this module also defines some convenience functions for -manipulating ranges: - -$(BOOKTABLE , - $(TR $(TD $(D $(LREF popFrontN))) - $(TD Advances a given _range by up to $(I n) elements. - )) - $(TR $(TD $(D $(LREF popBackN))) - $(TD Advances a given bidirectional _range from the right by up to - $(I n) elements. - )) - $(TR $(TD $(D $(LREF popFrontExactly))) - $(TD Advances a given _range by up exactly $(I n) elements. - )) - $(TR $(TD $(D $(LREF popBackExactly))) - $(TD Advances a given bidirectional _range from the right by exactly - $(I n) elements. - )) - $(TR $(TD $(D $(LREF moveFront))) - $(TD Removes the front element of a _range. - )) - $(TR $(TD $(D $(LREF moveBack))) - $(TD Removes the back element of a bidirectional _range. - )) - $(TR $(TD $(D $(LREF moveAt))) - $(TD Removes the $(I i)'th element of a random-access _range. - )) -) - -Source: $(PHOBOSSRC std/_range.d) - -Macros: - -WIKI = Phobos/StdRange - -Copyright: Copyright by authors 2008-. - -License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). - -Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha, -and Jonathan M Davis. Credit for some of the ideas in building this module goes -to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi). - */ -module std.range; - -public import std.array; -import std.algorithm : copy, count, equal, filter, filterBidirectional, - findSplitBefore, group, isSorted, joiner, move, map, max, min, sort, swap, - until; -import std.functional: unaryFun; -import std.traits; -import std.typecons : Flag, No, Tuple, tuple, Yes; -import std.typetuple : allSatisfy, staticMap, TypeTuple; - -// For testing only. This code is included in a string literal to be included -// in whatever module it's needed in, so that each module that uses it can be -// tested individually, without needing to link to std.range. -enum dummyRanges = q{ - // Used with the dummy ranges for testing higher order ranges. - enum RangeType - { - Input, - Forward, - Bidirectional, - Random - } - - enum Length - { - Yes, - No - } - - enum ReturnBy - { - Reference, - Value - } - - // Range that's useful for testing other higher order ranges, - // can be parametrized with attributes. It just dumbs down an array of - // numbers 1..10. - struct DummyRange(ReturnBy _r, Length _l, RangeType _rt) - { - // These enums are so that the template params are visible outside - // this instantiation. - enum r = _r; - enum l = _l; - enum rt = _rt; - - uint[] arr = [1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]; - - void reinit() - { - // Workaround for DMD bug 4378 - arr = [1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]; - } - - void popFront() - { - arr = arr[1..$]; - } - - @property bool empty() const - { - return arr.length == 0; - } - - static if(r == ReturnBy.Reference) - { - @property ref inout(uint) front() inout - { - return arr[0]; - } - - @property void front(uint val) - { - arr[0] = val; - } - } - else - { - @property uint front() const - { - return arr[0]; - } - } - - static if(rt >= RangeType.Forward) - { - @property typeof(this) save() - { - return this; - } - } - - static if(rt >= RangeType.Bidirectional) - { - void popBack() - { - arr = arr[0..$ - 1]; - } - - static if(r == ReturnBy.Reference) - { - @property ref inout(uint) back() inout - { - return arr[$ - 1]; - } - - @property void back(uint val) - { - arr[$ - 1] = val; - } - - } - else - { - @property uint back() const - { - return arr[$ - 1]; - } - } - } - - static if(rt >= RangeType.Random) - { - static if(r == ReturnBy.Reference) - { - ref inout(uint) opIndex(size_t index) inout - { - return arr[index]; - } - - void opIndexAssign(uint val, size_t index) - { - arr[index] = val; - } - } - else - { - uint opIndex(size_t index) const - { - return arr[index]; - } - } - - typeof(this) opSlice(size_t lower, size_t upper) - { - auto ret = this; - ret.arr = arr[lower..upper]; - return ret; - } - } - - static if(l == Length.Yes) - { - @property size_t length() const - { - return arr.length; - } - - alias opDollar = length; - } - } - - enum dummyLength = 10; - - alias AllDummyRanges = TypeTuple!( - DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), - DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), - DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), - DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), - DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional), - DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input), - DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward), - DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional), - DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), - DummyRange!(ReturnBy.Value, Length.No, RangeType.Input), - DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward), - DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional) - ); - -}; - -version(unittest) -{ - mixin(dummyRanges); - - // Tests whether forward, bidirectional and random access properties are - // propagated properly from the base range(s) R to the higher order range - // H. Useful in combination with DummyRange for testing several higher - // order ranges. - template propagatesRangeType(H, R...) { - static if(allSatisfy!(isRandomAccessRange, R)) { - enum bool propagatesRangeType = isRandomAccessRange!H; - } else static if(allSatisfy!(isBidirectionalRange, R)) { - enum bool propagatesRangeType = isBidirectionalRange!H; - } else static if(allSatisfy!(isForwardRange, R)) { - enum bool propagatesRangeType = isForwardRange!H; - } else { - enum bool propagatesRangeType = isInputRange!H; - } - } - - template propagatesLength(H, R...) { - static if(allSatisfy!(hasLength, R)) { - enum bool propagatesLength = hasLength!H; - } else { - enum bool propagatesLength = !hasLength!H; - } - } -} - -/** -Returns $(D true) if $(D R) is an input range. An input range must -define the primitives $(D empty), $(D popFront), and $(D front). The -following code should compile for any input range. - ----- -R r; // can define a range object -if (r.empty) {} // can test for empty -r.popFront(); // can invoke popFront() -auto h = r.front; // can get the front of the range of non-void type ----- - -The semantics of an input range (not checkable during compilation) are -assumed to be the following ($(D r) is an object of type $(D R)): - -$(UL $(LI $(D r.empty) returns $(D false) iff there is more data -available in the range.) $(LI $(D r.front) returns the current -element in the range. It may return by value or by reference. Calling -$(D r.front) is allowed only if calling $(D r.empty) has, or would -have, returned $(D false).) $(LI $(D r.popFront) advances to the next -element in the range. Calling $(D r.popFront) is allowed only if -calling $(D r.empty) has, or would have, returned $(D false).)) - */ -template isInputRange(R) -{ - enum bool isInputRange = is(typeof( - (inout int = 0) - { - R r = R.init; // can define a range object - if (r.empty) {} // can test for empty - r.popFront(); // can invoke popFront() - auto h = r.front; // can get the front of the range - })); -} - -unittest -{ - struct A {} - struct B - { - void popFront(); - @property bool empty(); - @property int front(); - } - static assert(!isInputRange!(A)); - static assert( isInputRange!(B)); - static assert( isInputRange!(int[])); - static assert( isInputRange!(char[])); - static assert(!isInputRange!(char[4])); - static assert( isInputRange!(inout(int)[])); // bug 7824 -} - -/+ -puts the whole raw element $(D e) into $(D r). doPut will not attempt to -iterate, slice or transcode $(D e) in any way shape or form. It will $(B only) -call the correct primitive ($(D r.put(e)), $(D r.front = e) or -$(D r(0)) once. - -This can be important when $(D e) needs to be placed in $(D r) unchanged. -Furthermore, it can be useful when working with $(D InputRange)s, as doPut -guarantees that no more than a single element will be placed. -+/ -package void doPut(R, E)(ref R r, auto ref E e) -{ - static if(is(PointerTarget!R == struct)) - enum usingPut = hasMember!(PointerTarget!R, "put"); - else - enum usingPut = hasMember!(R, "put"); - - static if (usingPut) - { - static assert(is(typeof(r.put(e))), - format("Cannot nativaly put a %s into a %s.", E.stringof, R.stringof)); - r.put(e); - } - else static if (isInputRange!R) - { - static assert(is(typeof(r.front = e)), - format("Cannot nativaly put a %s into a %s.", E.stringof, R.stringof)); - r.front = e; - r.popFront(); - } - else static if (is(typeof(r(e)))) - { - r(e); - } - else - { - import std.string; - static assert (false, - format("Cannot nativaly put a %s into a %s.", E.stringof, R.stringof)); - } -} - -unittest -{ - static assert (!isNativeOutputRange!(int, int)); - static assert ( isNativeOutputRange!(int[], int)); - static assert (!isNativeOutputRange!(int[][], int)); - - static assert (!isNativeOutputRange!(int, int[])); - static assert (!isNativeOutputRange!(int[], int[])); - static assert ( isNativeOutputRange!(int[][], int[])); - - static assert (!isNativeOutputRange!(int, int[][])); - static assert (!isNativeOutputRange!(int[], int[][])); - static assert (!isNativeOutputRange!(int[][], int[][])); - - static assert (!isNativeOutputRange!(int[4], int)); - static assert ( isNativeOutputRange!(int[4][], int)); //Scary! - static assert ( isNativeOutputRange!(int[4][], int[4])); - - static assert (!isNativeOutputRange!( char[], char)); - static assert (!isNativeOutputRange!( char[], dchar)); - static assert ( isNativeOutputRange!(dchar[], char)); - static assert ( isNativeOutputRange!(dchar[], dchar)); - -} - -/++ -Outputs $(D e) to $(D r). The exact effect is dependent upon the two -types. Several cases are accepted, as described below. The code snippets -are attempted in order, and the first to compile "wins" and gets -evaluated. - -In this table "doPut" is a method that places $(D e) into $(D r), using the -correct primitive: $(D r.put(e)) if $(D R) defines $(D put), $(D r.front = e) -if $(D r) is an input range (followed by $(D r.popFront())), or $(D r(e)) -otherwise. - -$(BOOKTABLE , - $(TR - $(TH Code Snippet) - $(TH Scenario) - ) - $(TR - $(TD $(D r.doPut(e);)) - $(TD $(D R) specifically accepts an $(D E).) - ) - $(TR - $(TD $(D r.doPut([ e ]);)) - $(TD $(D R) specifically accepts an $(D E[]).) - ) - $(TR - $(TD $(D r.putChar(e);)) - $(TD $(D R) accepts some form of string or character. put will - transcode the character $(D e) accordingly.) - ) - $(TR - $(TD $(D for (; !e.empty; e.popFront()) put(r, e.front);)) - $(TD Copying range $(D E) into $(D R).) - ) -) - -Tip: $(D put) should $(I not) be used "UFCS-style", e.g. $(D r.put(e)). -Doing this may call $(D R.put) directly, by-passing any transformation -feature provided by $(D Range.put). $(D put(r, e)) is prefered. - +/ -void put(R, E)(ref R r, E e) -{ - @property ref E[] EArrayInit(); //@@@9186@@@: Can't use (E[]).init - - //First level: simply straight up put. - static if (is(typeof(doPut(r, e)))) - { - doPut(r, e); - } - //Optional optimization block for straight up array to array copy. - else static if (isDynamicArray!R && !isNarrowString!R && isDynamicArray!E && is(typeof(r[] = e[]))) - { - immutable len = e.length; - r[0 .. len] = e[]; - r = r[len .. $]; - } - //Accepts E[] ? - else static if (is(typeof(doPut(r, [e]))) && !isDynamicArray!R) - { - if (__ctfe) - doPut(r, [e]); - else - doPut(r, (&e)[0..1]); - } - //special case for char to string. - else static if (isSomeChar!E && is(typeof(putChar(r, e)))) - { - putChar(r, e); - } - //Extract each element from the range - //We can use "put" here, so we can recursively test a RoR of E. - else static if (isInputRange!E && is(typeof(put(r, e.front)))) - { - //Special optimization: If E is a narrow string, and r accepts characters no-wider than the string's - //Then simply feed the characters 1 by 1. - static if (isNarrowString!E && ( - (is(E : const char[]) && is(typeof(doPut(r, char.max))) && !is(typeof(doPut(r, dchar.max))) && !is(typeof(doPut(r, wchar.max)))) || - (is(E : const wchar[]) && is(typeof(doPut(r, wchar.max))) && !is(typeof(doPut(r, dchar.max)))) ) ) - { - foreach(c; e) - doPut(r, c); - } - else - { - for (; !e.empty; e.popFront()) - put(r, e.front); - } - } - else - { - import std.string; - static assert (false, format("Cannot put a %s into a %s.", E.stringof, R.stringof)); - } -} - -//Helper function to handle chars as quickly and as elegantly as possible -//Assumes r.put(e)/r(e) has already been tested -private void putChar(R, E)(ref R r, E e) -if (isSomeChar!E) -{ - ////@@@9186@@@: Can't use (E[]).init - ref const( char)[] cstringInit(); - ref const(wchar)[] wstringInit(); - ref const(dchar)[] dstringInit(); - - enum csCond = !isDynamicArray!R && is(typeof(doPut(r, cstringInit()))); - enum wsCond = !isDynamicArray!R && is(typeof(doPut(r, wstringInit()))); - enum dsCond = !isDynamicArray!R && is(typeof(doPut(r, dstringInit()))); - - //Use "max" to avoid static type demotion - enum ccCond = is(typeof(doPut(r, char.max))); - enum wcCond = is(typeof(doPut(r, wchar.max))); - //enum dcCond = is(typeof(doPut(r, dchar.max))); - - //Fast transform a narrow char into a wider string - static if ((wsCond && E.sizeof < wchar.sizeof) || (dsCond && E.sizeof < dchar.sizeof)) - { - enum w = wsCond && E.sizeof < wchar.sizeof; - Select!(w, wchar, dchar) c = e; - if (__ctfe) - doPut(r, [c]); - else - doPut(r, (&c)[0..1]); - } - //Encode a wide char into a narrower string - else static if (wsCond || csCond) - { - import std.utf; - /+static+/ Select!(wsCond, wchar[2], char[4]) buf; //static prevents purity. - doPut(r, buf.ptr[0 .. encode(buf, e)]); //the word ".ptr" added to enforce un-safety. - } - //Slowly encode a wide char into a series of narrower chars - else static if (wcCond || ccCond) - { - import std.encoding; - alias C = Select!(wcCond, wchar, char); - encode!(C, R)(e, r); - } - else - { - import std.string; - static assert (false, format("Cannot put a %s into a %s.", E.stringof, R.stringof)); - } -} - -pure unittest -{ - auto f = delegate (const(char)[]) {}; - putChar(f, cast(dchar)'a'); -} - -unittest -{ - struct A {} - static assert(!isInputRange!(A)); - struct B - { - void put(int) {} - } - B b; - put(b, 5); -} - -unittest -{ - int[] a = [1, 2, 3], b = [10, 20]; - auto c = a; - put(a, b); - assert(c == [10, 20, 3]); - assert(a == [3]); -} - -unittest -{ - int[] a = new int[10]; - int b; - static assert(isInputRange!(typeof(a))); - put(a, b); -} - -unittest -{ - void myprint(in char[] s) { } - auto r = &myprint; - put(r, 'a'); -} - -unittest -{ - int[] a = new int[10]; - static assert(!__traits(compiles, put(a, 1.0L))); - static assert( __traits(compiles, put(a, 1))); - /* - * a[0] = 65; // OK - * a[0] = 'A'; // OK - * a[0] = "ABC"[0]; // OK - * put(a, "ABC"); // OK - */ - static assert( __traits(compiles, put(a, "ABC"))); -} - -unittest -{ - char[] a = new char[10]; - static assert(!__traits(compiles, put(a, 1.0L))); - static assert(!__traits(compiles, put(a, 1))); - // char[] is NOT output range. - static assert(!__traits(compiles, put(a, 'a'))); - static assert(!__traits(compiles, put(a, "ABC"))); -} - -unittest -{ - int[][] a; - int[] b; - int c; - static assert( __traits(compiles, put(b, c))); - static assert( __traits(compiles, put(a, b))); - static assert(!__traits(compiles, put(a, c))); -} - -unittest -{ - int[][] a = new int[][](3); - int[] b = [1]; - auto aa = a; - put(aa, b); - assert(aa == [[], []]); - assert(a == [[1], [], []]); - int[][3] c = [2]; - aa = a; - put(aa, c[]); - assert(aa.empty); - assert(a == [[2], [2], [2]]); -} - -unittest -{ - // Test fix for bug 7476. - struct LockingTextWriter - { - void put(dchar c){} - } - struct RetroResult - { - bool end = false; - @property bool empty() const { return end; } - @property dchar front(){ return 'a'; } - void popFront(){ end = true; } - } - LockingTextWriter w; - RetroResult r; - put(w, r); -} - -unittest -{ - import std.conv : to; - import std.typecons : tuple; - - static struct PutC(C) - { - string result; - void put(const(C) c) { result ~= to!string((&c)[0..1]); } - } - static struct PutS(C) - { - string result; - void put(const(C)[] s) { result ~= to!string(s); } - } - static struct PutSS(C) - { - string result; - void put(const(C)[][] ss) - { - foreach(s; ss) - result ~= to!string(s); - } - } - - PutS!char p; - putChar(p, cast(dchar)'a'); - - //Source Char - foreach (SC; TypeTuple!(char, wchar, dchar)) - { - SC ch = 'I'; - dchar dh = '♥'; - immutable(SC)[] s = "日本語!"; - immutable(SC)[][] ss = ["日本語", "が", "好き", "ですか", "?"]; - - //Target Char - foreach (TC; TypeTuple!(char, wchar, dchar)) - { - //Testing PutC and PutS - foreach (Type; TypeTuple!(PutC!TC, PutS!TC)) - { - Type type; - auto sink = new Type(); - - //Testing put and sink - foreach (value ; tuple(type, sink)) - { - put(value, ch); - assert(value.result == "I"); - put(value, dh); - assert(value.result == "I♥"); - put(value, s); - assert(value.result == "I♥日本語!"); - put(value, ss); - assert(value.result == "I♥日本語!日本語が好きですか?"); - } - } - } - } -} - -unittest -{ - static struct CharRange - { - char c; - enum empty = false; - void popFront(){}; - ref char front() @property - { - return c; - } - } - CharRange c; - put(c, cast(dchar)'H'); - put(c, "hello"d); -} - -unittest -{ - // issue 9823 - const(char)[] r; - void delegate(const(char)[]) dg = (s) { r = s; }; - put(dg, ["ABC"]); - assert(r == "ABC"); -} - -unittest -{ - // issue 10571 - import std.format; - string buf; - formattedWrite((in char[] s) { buf ~= s; }, "%s", "hello"); - assert(buf == "hello"); -} - -unittest -{ - import std.format; - struct PutC(C) - { - void put(C){} - } - struct PutS(C) - { - void put(const(C)[]){} - } - struct CallC(C) - { - void opCall(C){} - } - struct CallS(C) - { - void opCall(const(C)[]){} - } - struct FrontC(C) - { - enum empty = false; - auto front()@property{return C.init;} - void front(C)@property{} - void popFront(){} - } - struct FrontS(C) - { - enum empty = false; - auto front()@property{return C[].init;} - void front(const(C)[])@property{} - void popFront(){} - } - void foo() - { - foreach(C; TypeTuple!(char, wchar, dchar)) - { - formattedWrite((C c){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - formattedWrite((const(C)[]){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - formattedWrite(PutC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - formattedWrite(PutS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - CallC!C callC; - CallS!C callS; - formattedWrite(callC, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - formattedWrite(callS, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - formattedWrite(FrontC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - formattedWrite(FrontS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - } - formattedWrite((dchar[]).init, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); - } -} - -/+ -Returns $(D true) if $(D R) is a native output range for elements of type -$(D E). An output range is defined functionally as a range that -supports the operation $(D doPut(r, e)) as defined above. if $(D doPut(r, e)) -is valid, then $(D put(r,e)) will have the same behavior. - -The two guarantees isNativeOutputRange gives over the larger $(D isOutputRange) -are: -1: $(D e) is $(B exactly) what will be placed (not $(D [e]), for example). -2: if $(D E) is a non $(empty) $(D InputRange), then placing $(D e) is -guaranteed to not overflow the range. - +/ -package template isNativeOutputRange(R, E) -{ - enum bool isNativeOutputRange = is(typeof( - (inout int = 0) - { - R r = void; - E e; - doPut(r, e); - })); -} -// -unittest -{ - int[] r = new int[](4); - static assert(isInputRange!(int[])); - static assert( isNativeOutputRange!(int[], int)); - static assert(!isNativeOutputRange!(int[], int[])); - static assert( isOutputRange!(int[], int[])); - - if (!r.empty) - put(r, 1); //guaranteed to succeed - if (!r.empty) - put(r, [1, 2]); //May actually error out. -} -/++ -Returns $(D true) if $(D R) is an output range for elements of type -$(D E). An output range is defined functionally as a range that -supports the operation $(D put(r, e)) as defined above. - +/ -template isOutputRange(R, E) -{ - enum bool isOutputRange = is(typeof( - (inout int = 0) - { - R r = R.init; - E e = E.init; - put(r, e); - })); -} - -/// -unittest -{ - void myprint(in char[] s) { } - static assert(isOutputRange!(typeof(&myprint), char)); - - static assert(!isOutputRange!(char[], char)); - static assert( isOutputRange!(dchar[], wchar)); - static assert( isOutputRange!(dchar[], dchar)); -} - -unittest -{ - import std.stdio : writeln; - - auto app = appender!string(); - string s; - static assert( isOutputRange!(Appender!string, string)); - static assert( isOutputRange!(Appender!string*, string)); - static assert(!isOutputRange!(Appender!string, int)); - static assert(!isOutputRange!(wchar[], wchar)); - static assert( isOutputRange!(dchar[], char)); - static assert( isOutputRange!(dchar[], string)); - static assert( isOutputRange!(dchar[], wstring)); - static assert( isOutputRange!(dchar[], dstring)); - - static assert(!isOutputRange!(const(int)[], int)); - static assert(!isOutputRange!(inout(int)[], int)); -} - -unittest -{ - // 6973 - static assert(isOutputRange!(OutputRange!int, int)); -} - -/** -Returns $(D true) if $(D R) is a forward range. A forward range is an -input range $(D r) that can save "checkpoints" by saving $(D r.save) -to another value of type $(D R). Notable examples of input ranges that -are $(I not) forward ranges are file/socket ranges; copying such a -range will not save the position in the stream, and they most likely -reuse an internal buffer as the entire stream does not sit in -memory. Subsequently, advancing either the original or the copy will -advance the stream, so the copies are not independent. - -The following code should compile for any forward range. - ----- -static assert(isInputRange!R); -R r1; -static assert (is(typeof(r1.save) == R)); ----- - -Saving a range is not duplicating it; in the example above, $(D r1) -and $(D r2) still refer to the same underlying data. They just -navigate that data independently. - -The semantics of a forward range (not checkable during compilation) -are the same as for an input range, with the additional requirement -that backtracking must be possible by saving a copy of the range -object with $(D save) and using it later. - */ -template isForwardRange(R) -{ - enum bool isForwardRange = isInputRange!R && is(typeof( - (inout int = 0) - { - R r1 = R.init; - static assert (is(typeof(r1.save) == R)); - })); -} - -unittest -{ - static assert(!isForwardRange!(int)); - static assert( isForwardRange!(int[])); - static assert( isForwardRange!(inout(int)[])); -} - -/** -Returns $(D true) if $(D R) is a bidirectional range. A bidirectional -range is a forward range that also offers the primitives $(D back) and -$(D popBack). The following code should compile for any bidirectional -range. - ----- -R r; -static assert(isForwardRange!R); // is forward range -r.popBack(); // can invoke popBack -auto t = r.back; // can get the back of the range -auto w = r.front; -static assert(is(typeof(t) == typeof(w))); // same type for front and back ----- - -The semantics of a bidirectional range (not checkable during -compilation) are assumed to be the following ($(D r) is an object of -type $(D R)): - -$(UL $(LI $(D r.back) returns (possibly a reference to) the last -element in the range. Calling $(D r.back) is allowed only if calling -$(D r.empty) has, or would have, returned $(D false).)) - */ -template isBidirectionalRange(R) -{ - enum bool isBidirectionalRange = isForwardRange!R && is(typeof( - (inout int = 0) - { - R r = R.init; - r.popBack(); - auto t = r.back; - auto w = r.front; - static assert(is(typeof(t) == typeof(w))); - })); -} - -unittest -{ - struct A {} - struct B - { - void popFront(); - @property bool empty(); - @property int front(); - } - struct C - { - @property bool empty(); - @property C save(); - void popFront(); - @property int front(); - void popBack(); - @property int back(); - } - static assert(!isBidirectionalRange!(A)); - static assert(!isBidirectionalRange!(B)); - static assert( isBidirectionalRange!(C)); - static assert( isBidirectionalRange!(int[])); - static assert( isBidirectionalRange!(char[])); - static assert( isBidirectionalRange!(inout(int)[])); -} - -/** -Returns $(D true) if $(D R) is a random-access range. A random-access -range is a bidirectional range that also offers the primitive $(D -opIndex), OR an infinite forward range that offers $(D opIndex). In -either case, the range must either offer $(D length) or be -infinite. The following code should compile for any random-access -range. - ----- -// range is finite and bidirectional or infinite and forward. -static assert(isBidirectionalRange!R || - isForwardRange!R && isInfinite!R); - -R r = void; -auto e = r[1]; // can index -static assert(is(typeof(e) == typeof(r.front))); // same type for indexed and front -static assert(!isNarrowString!R); // narrow strings cannot be indexed as ranges -static assert(hasLength!R || isInfinite!R); // must have length or be infinite - -// $ must work as it does with arrays if opIndex works with $ -static if(is(typeof(r[$]))) -{ - static assert(is(typeof(r.front) == typeof(r[$]))); - - // $ - 1 doesn't make sense with infinite ranges but needs to work - // with finite ones. - static if(!isInfinite!R) - static assert(is(typeof(r.front) == typeof(r[$ - 1]))); -} ----- - -The semantics of a random-access range (not checkable during -compilation) are assumed to be the following ($(D r) is an object of -type $(D R)): $(UL $(LI $(D r.opIndex(n)) returns a reference to the -$(D n)th element in the range.)) - -Although $(D char[]) and $(D wchar[]) (as well as their qualified -versions including $(D string) and $(D wstring)) are arrays, $(D -isRandomAccessRange) yields $(D false) for them because they use -variable-length encodings (UTF-8 and UTF-16 respectively). These types -are bidirectional ranges only. - */ -template isRandomAccessRange(R) -{ - enum bool isRandomAccessRange = is(typeof( - (inout int = 0) - { - static assert(isBidirectionalRange!R || - isForwardRange!R && isInfinite!R); - R r = R.init; - auto e = r[1]; - static assert(is(typeof(e) == typeof(r.front))); - static assert(!isNarrowString!R); - static assert(hasLength!R || isInfinite!R); - - static if(is(typeof(r[$]))) - { - static assert(is(typeof(r.front) == typeof(r[$]))); - - static if(!isInfinite!R) - static assert(is(typeof(r.front) == typeof(r[$ - 1]))); - } - })); -} - -unittest -{ - struct A {} - struct B - { - void popFront(); - @property bool empty(); - @property int front(); - } - struct C - { - void popFront(); - @property bool empty(); - @property int front(); - void popBack(); - @property int back(); - } - struct D - { - @property bool empty(); - @property D save(); - @property int front(); - void popFront(); - @property int back(); - void popBack(); - ref int opIndex(uint); - @property size_t length(); - alias opDollar = length; - //int opSlice(uint, uint); - } - static assert(!isRandomAccessRange!(A)); - static assert(!isRandomAccessRange!(B)); - static assert(!isRandomAccessRange!(C)); - static assert( isRandomAccessRange!(D)); - static assert( isRandomAccessRange!(int[])); - static assert( isRandomAccessRange!(inout(int)[])); -} - -unittest -{ - // Test fix for bug 6935. - struct R - { - @disable this(); - - @property bool empty() const { return false; } - @property int front() const { return 0; } - void popFront() {} - - @property R save() { return this; } - - @property int back() const { return 0; } - void popBack(){} - - int opIndex(size_t n) const { return 0; } - @property size_t length() const { return 0; } - alias opDollar = length; - - void put(int e){ } - } - static assert(isInputRange!R); - static assert(isForwardRange!R); - static assert(isBidirectionalRange!R); - static assert(isRandomAccessRange!R); - static assert(isOutputRange!(R, int)); -} - -/** -Returns $(D true) iff $(D R) is an input range that supports the -$(D moveFront) primitive, as well as $(D moveBack) and $(D moveAt) if it's a -bidirectional or random access range. These may be explicitly implemented, or -may work via the default behavior of the module level functions $(D moveFront) -and friends. The following code should compile for any range -with mobile elements. - ----- -alias E = ElementType!R; -R r; -static assert(isInputRange!R); -static assert(is(typeof(moveFront(r)) == E)); -static if (isBidirectionalRange!R) - static assert(is(typeof(moveBack(r)) == E)); -static if (isRandomAccessRange!R) - static assert(is(typeof(moveAt(r, 0)) == E)); ----- - */ -template hasMobileElements(R) -{ - enum bool hasMobileElements = isInputRange!R && is(typeof( - (inout int = 0) - { - alias E = ElementType!R; - R r = R.init; - static assert(is(typeof(moveFront(r)) == E)); - static if (isBidirectionalRange!R) - static assert(is(typeof(moveBack(r)) == E)); - static if (isRandomAccessRange!R) - static assert(is(typeof(moveAt(r, 0)) == E)); - })); -} - -/// -unittest -{ - static struct HasPostblit - { - this(this) {} - } - - auto nonMobile = map!"a"(repeat(HasPostblit.init)); - static assert(!hasMobileElements!(typeof(nonMobile))); - static assert( hasMobileElements!(int[])); - static assert( hasMobileElements!(inout(int)[])); - static assert( hasMobileElements!(typeof(iota(1000)))); - - static assert( hasMobileElements!( string)); - static assert( hasMobileElements!(dstring)); - static assert( hasMobileElements!( char[])); - static assert( hasMobileElements!(dchar[])); -} - -/** -The element type of $(D R). $(D R) does not have to be a range. The -element type is determined as the type yielded by $(D r.front) for an -object $(D r) of type $(D R). For example, $(D ElementType!(T[])) is -$(D T) if $(D T[]) isn't a narrow string; if it is, the element type is -$(D dchar). If $(D R) doesn't have $(D front), $(D ElementType!R) is -$(D void). - */ -template ElementType(R) -{ - static if (is(typeof(R.init.front.init) T)) - alias ElementType = T; - else - alias ElementType = void; -} - -/// -unittest -{ - // Standard arrays: returns the type of the elements of the array - static assert(is(ElementType!(int[]) == int)); - - // Accessing .front retrieves the decoded dchar - static assert(is(ElementType!(char[]) == dchar)); // rvalue - static assert(is(ElementType!(dchar[]) == dchar)); // lvalue - - // Ditto - static assert(is(ElementType!(string) == dchar)); - static assert(is(ElementType!(dstring) == immutable(dchar))); - - // For ranges it gets the type of .front. - auto range = iota(0, 10); - static assert(is(ElementType!(typeof(range)) == int)); -} - -unittest -{ - static assert(is(ElementType!(byte[]) == byte)); - static assert(is(ElementType!(wchar[]) == dchar)); // rvalue - static assert(is(ElementType!(wstring) == dchar)); -} - -unittest -{ - enum XYZ : string { a = "foo" } - auto x = XYZ.a.front; - immutable char[3] a = "abc"; - int[] i; - void[] buf; - static assert(is(ElementType!(XYZ) == dchar)); - static assert(is(ElementType!(typeof(a)) == dchar)); - static assert(is(ElementType!(typeof(i)) == int)); - static assert(is(ElementType!(typeof(buf)) == void)); - static assert(is(ElementType!(inout(int)[]) == inout(int))); - static assert(is(ElementType!(inout(int[])) == inout(int))); -} - -unittest -{ - static assert(is(ElementType!(int[5]) == int)); - static assert(is(ElementType!(int[0]) == int)); - static assert(is(ElementType!(char[5]) == dchar)); - static assert(is(ElementType!(char[0]) == dchar)); -} - -unittest //11336 -{ - static struct S - { - this(this) @disable; - } - static assert(is(ElementType!(S[]) == S)); -} - -unittest // 11401 -{ - // ElementType should also work for non-@propety 'front' - struct E { ushort id; } - struct R - { - E front() { return E.init; } - } - static assert(is(ElementType!R == E)); -} - -/** -The encoding element type of $(D R). For narrow strings ($(D char[]), -$(D wchar[]) and their qualified variants including $(D string) and -$(D wstring)), $(D ElementEncodingType) is the character type of the -string. For all other types, $(D ElementEncodingType) is the same as -$(D ElementType). - */ -template ElementEncodingType(R) -{ - static if (is(StringTypeOf!R) && is(R : E[], E)) - alias ElementEncodingType = E; - else - alias ElementEncodingType = ElementType!R; -} - -/// -unittest -{ - // internally the range stores the encoded type - static assert(is(ElementEncodingType!(char[]) == char)); - - static assert(is(ElementEncodingType!(wstring) == immutable(wchar))); - - static assert(is(ElementEncodingType!(byte[]) == byte)); - - auto range = iota(0, 10); - static assert(is(ElementEncodingType!(typeof(range)) == int)); -} - -unittest -{ - static assert(is(ElementEncodingType!(wchar[]) == wchar)); - static assert(is(ElementEncodingType!(dchar[]) == dchar)); - static assert(is(ElementEncodingType!(string) == immutable(char))); - static assert(is(ElementEncodingType!(dstring) == immutable(dchar))); - static assert(is(ElementEncodingType!(int[]) == int)); -} - -unittest -{ - enum XYZ : string { a = "foo" } - auto x = XYZ.a.front; - immutable char[3] a = "abc"; - int[] i; - void[] buf; - static assert(is(ElementType!(XYZ) : dchar)); - static assert(is(ElementEncodingType!(char[]) == char)); - static assert(is(ElementEncodingType!(string) == immutable char)); - static assert(is(ElementType!(typeof(a)) : dchar)); - static assert(is(ElementType!(typeof(i)) == int)); - static assert(is(ElementEncodingType!(typeof(i)) == int)); - static assert(is(ElementType!(typeof(buf)) : void)); - - static assert(is(ElementEncodingType!(inout char[]) : inout(char))); -} - -unittest -{ - static assert(is(ElementEncodingType!(int[5]) == int)); - static assert(is(ElementEncodingType!(int[0]) == int)); - static assert(is(ElementEncodingType!(char[5]) == char)); - static assert(is(ElementEncodingType!(char[0]) == char)); -} - -/** -Returns $(D true) if $(D R) is an input range and has swappable -elements. The following code should compile for any range -with swappable elements. - ----- -R r; -static assert(isInputRange!R); -swap(r.front, r.front); -static if (isBidirectionalRange!R) swap(r.back, r.front); -static if (isRandomAccessRange!R) swap(r[], r.front); ----- - */ -template hasSwappableElements(R) -{ - enum bool hasSwappableElements = isInputRange!R && is(typeof( - (inout int = 0) - { - R r = R.init; - swap(r.front, r.front); - static if (isBidirectionalRange!R) swap(r.back, r.front); - static if (isRandomAccessRange!R) swap(r[0], r.front); - })); -} - -/// -unittest -{ - static assert(!hasSwappableElements!(const int[])); - static assert(!hasSwappableElements!(const(int)[])); - static assert(!hasSwappableElements!(inout(int)[])); - static assert( hasSwappableElements!(int[])); - - static assert(!hasSwappableElements!( string)); - static assert(!hasSwappableElements!(dstring)); - static assert(!hasSwappableElements!( char[])); - static assert( hasSwappableElements!(dchar[])); -} - -/** -Returns $(D true) if $(D R) is an input range and has mutable -elements. The following code should compile for any range -with assignable elements. - ----- -R r; -static assert(isInputRange!R); -r.front = r.front; -static if (isBidirectionalRange!R) r.back = r.front; -static if (isRandomAccessRange!R) r[0] = r.front; ----- - */ -template hasAssignableElements(R) -{ - enum bool hasAssignableElements = isInputRange!R && is(typeof( - (inout int = 0) - { - R r = R.init; - r.front = r.front; - static if (isBidirectionalRange!R) r.back = r.front; - static if (isRandomAccessRange!R) r[0] = r.front; - })); -} - -/// -unittest -{ - static assert(!hasAssignableElements!(const int[])); - static assert(!hasAssignableElements!(const(int)[])); - static assert( hasAssignableElements!(int[])); - static assert(!hasAssignableElements!(inout(int)[])); - - static assert(!hasAssignableElements!( string)); - static assert(!hasAssignableElements!(dstring)); - static assert(!hasAssignableElements!( char[])); - static assert( hasAssignableElements!(dchar[])); -} - -/** -Tests whether the range $(D R) has lvalue elements. These are defined as -elements that can be passed by reference and have their address taken. -The following code should compile for any range with lvalue elements. ----- -void passByRef(ref ElementType!R stuff); -... -static assert(isInputRange!R); -passByRef(r.front); -static if (isBidirectionalRange!R) passByRef(r.back); -static if (isRandomAccessRange!R) passByRef(r[0]); ----- -*/ -template hasLvalueElements(R) -{ - enum bool hasLvalueElements = isInputRange!R && is(typeof( - (inout int = 0) - { - void checkRef(ref ElementType!R stuff); - R r = R.init; - - checkRef(r.front); - static if (isBidirectionalRange!R) checkRef(r.back); - static if (isRandomAccessRange!R) checkRef(r[0]); - })); -} - -/// -unittest -{ - static assert( hasLvalueElements!(int[])); - static assert( hasLvalueElements!(const(int)[])); - static assert( hasLvalueElements!(inout(int)[])); - static assert( hasLvalueElements!(immutable(int)[])); - static assert(!hasLvalueElements!(typeof(iota(3)))); - - static assert(!hasLvalueElements!( string)); - static assert( hasLvalueElements!(dstring)); - static assert(!hasLvalueElements!( char[])); - static assert( hasLvalueElements!(dchar[])); - - auto c = chain([1, 2, 3], [4, 5, 6]); - static assert( hasLvalueElements!(typeof(c))); -} - -unittest -{ - // bugfix 6336 - struct S { immutable int value; } - static assert( isInputRange!(S[])); - static assert( hasLvalueElements!(S[])); -} - -/** -Returns $(D true) if $(D R) has a $(D length) member that returns an -integral type. $(D R) does not have to be a range. Note that $(D -length) is an optional primitive as no range must implement it. Some -ranges do not store their length explicitly, some cannot compute it -without actually exhausting the range (e.g. socket streams), and some -other ranges may be infinite. - -Although narrow string types ($(D char[]), $(D wchar[]), and their -qualified derivatives) do define a $(D length) property, $(D -hasLength) yields $(D false) for them. This is because a narrow -string's length does not reflect the number of characters, but instead -the number of encoding units, and as such is not useful with -range-oriented algorithms. - */ -template hasLength(R) -{ - enum bool hasLength = !isNarrowString!R && is(typeof( - (inout int = 0) - { - R r = R.init; - static assert(is(typeof(r.length) : ulong)); - })); -} - -/// -unittest -{ - static assert(!hasLength!(char[])); - static assert( hasLength!(int[])); - static assert( hasLength!(inout(int)[])); - - struct A { ulong length; } - struct B { size_t length() { return 0; } } - struct C { @property size_t length() { return 0; } } - static assert( hasLength!(A)); - static assert(!hasLength!(B)); - static assert( hasLength!(C)); -} - -/** -Returns $(D true) if $(D R) is an infinite input range. An -infinite input range is an input range that has a statically-defined -enumerated member called $(D empty) that is always $(D false), -for example: - ----- -struct MyInfiniteRange -{ - enum bool empty = false; - ... -} ----- - */ +Submodules: -template isInfinite(R) -{ - static if (isInputRange!R && __traits(compiles, { enum e = R.empty; })) - enum bool isInfinite = !R.empty; - else - enum bool isInfinite = false; -} +This module has a few submodules: -/// -unittest -{ - static assert(!isInfinite!(int[])); - static assert( isInfinite!(Repeat!(int))); -} +The $(LINK2 std_range_primitives.html, $(D std._range.primitives)) submodule +provides basic _range functionality. It defines several templates for testing +whether a given object is a _range, what kind of _range it is, and provides +some common _range operations. -/** -Returns $(D true) if $(D R) offers a slicing operator with integral boundaries -that returns a forward range type. +The $(LINK2 std_range_interfaces.html, $(D std._range.interfaces)) submodule +provides object-based interfaces for working with ranges via runtime +polymorphism. -For finite ranges, the result of $(D opSlice) must be of the same type as the -original range type. If the range defines $(D opDollar), then it must support -subtraction. +The remainder of this module provides a rich set of _range creation and +composition templates that let you construct new ranges out of existing ranges: -For infinite ranges, when $(I not) using $(D opDollar), the result of -$(D opSlice) must be the result of $(LREF take) or $(LREF takeExactly) on the -original range (they both return the same type for infinite ranges). However, -when using $(D opDollar), the result of $(D opSlice) must be that of the -original range type. +$(BOOKTABLE , + $(TR $(TD $(D $(LREF retro))) + $(TD Iterates a bidirectional _range backwards. + )) + $(TR $(TD $(D $(LREF stride))) + $(TD Iterates a _range with stride $(I n). + )) + $(TR $(TD $(D $(LREF chain))) + $(TD Concatenates several ranges into a single _range. + )) + $(TR $(TD $(D $(LREF roundRobin))) + $(TD Given $(I n) ranges, creates a new _range that return the $(I n) + first elements of each _range, in turn, then the second element of each + _range, and so on, in a round-robin fashion. + )) + $(TR $(TD $(D $(LREF radial))) + $(TD Given a random-access _range and a starting point, creates a + _range that alternately returns the next left and next right element to + the starting point. + )) + $(TR $(TD $(D $(LREF take))) + $(TD Creates a sub-_range consisting of only up to the first $(I n) + elements of the given _range. + )) + $(TR $(TD $(D $(LREF takeExactly))) + $(TD Like $(D take), but assumes the given _range actually has $(I n) + elements, and therefore also defines the $(D length) property. + )) + $(TR $(TD $(D $(LREF takeOne))) + $(TD Creates a random-access _range consisting of exactly the first + element of the given _range. + )) + $(TR $(TD $(D $(LREF takeNone))) + $(TD Creates a random-access _range consisting of zero elements of the + given _range. + )) + $(TR $(TD $(D $(LREF drop))) + $(TD Creates the _range that results from discarding the first $(I n) + elements from the given _range. + )) + $(TR $(TD $(D $(LREF dropExactly))) + $(TD Creates the _range that results from discarding exactly $(I n) + of the first elements from the given _range. + )) + $(TR $(TD $(D $(LREF dropOne))) + $(TD Creates the _range that results from discarding + the first elements from the given _range. + )) + $(TR $(TD $(D $(LREF repeat))) + $(TD Creates a _range that consists of a single element repeated $(I n) + times, or an infinite _range repeating that element indefinitely. + )) + $(TR $(TD $(D $(LREF cycle))) + $(TD Creates an infinite _range that repeats the given forward _range + indefinitely. Good for implementing circular buffers. + )) + $(TR $(TD $(D $(LREF zip))) + $(TD Given $(I n) _ranges, creates a _range that successively returns a + tuple of all the first elements, a tuple of all the second elements, + etc. + )) + $(TR $(TD $(D $(LREF lockstep))) + $(TD Iterates $(I n) _ranges in lockstep, for use in a $(D foreach) + loop. Similar to $(D zip), except that $(D lockstep) is designed + especially for $(D foreach) loops. + )) + $(TR $(TD $(D $(LREF recurrence))) + $(TD Creates a forward _range whose values are defined by a + mathematical recurrence relation. + )) + $(TR $(TD $(D $(LREF sequence))) + $(TD Similar to $(D recurrence), except that a random-access _range is + created. + )) + $(TR $(TD $(D $(LREF iota))) + $(TD Creates a _range consisting of numbers between a starting point + and ending point, spaced apart by a given interval. + )) + $(TR $(TD $(D $(LREF frontTransversal))) + $(TD Creates a _range that iterates over the first elements of the + given ranges. + )) + $(TR $(TD $(D $(LREF transversal))) + $(TD Creates a _range that iterates over the $(I n)'th elements of the + given random-access ranges. + )) + $(TR $(TD $(D $(LREF transposed))) + $(TD Transposes a _range of ranges. + )) + $(TR $(TD $(D $(LREF indexed))) + $(TD Creates a _range that offers a view of a given _range as though + its elements were reordered according to a given _range of indices. + )) + $(TR $(TD $(D $(LREF chunks))) + $(TD Creates a _range that returns fixed-size chunks of the original + _range. + )) + $(TR $(TD $(D $(LREF only))) + $(TD Creates a _range that iterates over the given arguments. + )) + $(TR $(TD $(D $(LREF tee))) + $(TD Creates a _range that wraps a given _range, forwarding along + its elements while also calling a provided function with each element. + )) + $(TR $(TD $(D $(LREF enumerate))) + $(TD Iterates a _range with an attached index variable. + )) + $(TR $(TD $(D $(LREF NullSink))) + $(TD An output _range that discards the data it receives. + )) +) -The following code must compile for $(D hasSlicing) to be $(D true): +Ranges whose elements are sorted afford better efficiency with certain +operations. For this, the $(D $(LREF assumeSorted)) function can be used to +construct a $(D $(LREF SortedRange)) from a pre-sorted _range. The $(LINK2 +std_algorithm.html#sort, $(D std.algorithm.sort)) function also conveniently +returns a $(D SortedRange). $(D SortedRange) objects provide some additional +_range operations that take advantage of the fact that the _range is sorted. ----- -R r = void; +Source: $(PHOBOSSRC std/_range/_package.d) -static if(isInfinite!R) - typeof(take(r, 1)) s = r[1 .. 2]; -else -{ - static assert(is(typeof(r[1 .. 2]) == R)); - R s = r[1 .. 2]; -} +Macros: -s = r[1 .. 2]; +WIKI = Phobos/StdRange -static if(is(typeof(r[0 .. $]))) -{ - static assert(is(typeof(r[0 .. $]) == R)); - R t = r[0 .. $]; - t = r[0 .. $]; +Copyright: Copyright by authors 2008-. - static if(!isInfinite!R) - { - static assert(is(typeof(r[0 .. $ - 1]) == R)); - R u = r[0 .. $ - 1]; - u = r[0 .. $ - 1]; - } -} +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). -static assert(isForwardRange!(typeof(r[1 .. 2]))); -static assert(hasLength!(typeof(r[1 .. 2]))); ----- +Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha, +and Jonathan M Davis. Credit for some of the ideas in building this module goes +to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi). */ -template hasSlicing(R) -{ - enum bool hasSlicing = isForwardRange!R && !isNarrowString!R && is(typeof( - (inout int = 0) - { - R r = R.init; - - static if(isInfinite!R) - typeof(take(r, 1)) s = r[1 .. 2]; - else - { - static assert(is(typeof(r[1 .. 2]) == R)); - R s = r[1 .. 2]; - } - - s = r[1 .. 2]; - - static if(is(typeof(r[0 .. $]))) - { - static assert(is(typeof(r[0 .. $]) == R)); - R t = r[0 .. $]; - t = r[0 .. $]; - - static if(!isInfinite!R) - { - static assert(is(typeof(r[0 .. $ - 1]) == R)); - R u = r[0 .. $ - 1]; - u = r[0 .. $ - 1]; - } - } - - static assert(isForwardRange!(typeof(r[1 .. 2]))); - static assert(hasLength!(typeof(r[1 .. 2]))); - })); -} - -/// -unittest -{ - static assert( hasSlicing!(int[])); - static assert( hasSlicing!(const(int)[])); - static assert(!hasSlicing!(const int[])); - static assert( hasSlicing!(inout(int)[])); - static assert(!hasSlicing!(inout int [])); - static assert( hasSlicing!(immutable(int)[])); - static assert(!hasSlicing!(immutable int[])); - static assert(!hasSlicing!string); - static assert( hasSlicing!dstring); - - enum rangeFuncs = "@property int front();" ~ - "void popFront();" ~ - "@property bool empty();" ~ - "@property auto save() { return this; }" ~ - "@property size_t length();"; - - struct A { mixin(rangeFuncs); int opSlice(size_t, size_t); } - struct B { mixin(rangeFuncs); B opSlice(size_t, size_t); } - struct C { mixin(rangeFuncs); @disable this(); C opSlice(size_t, size_t); } - struct D { mixin(rangeFuncs); int[] opSlice(size_t, size_t); } - static assert(!hasSlicing!(A)); - static assert( hasSlicing!(B)); - static assert( hasSlicing!(C)); - static assert(!hasSlicing!(D)); - - struct InfOnes - { - enum empty = false; - void popFront() {} - @property int front() { return 1; } - @property InfOnes save() { return this; } - auto opSlice(size_t i, size_t j) { return takeExactly(this, j - i); } - auto opSlice(size_t i, Dollar d) { return this; } - - struct Dollar {} - Dollar opDollar() const { return Dollar.init; } - } - - static assert(hasSlicing!InfOnes); -} - -/** -This is a best-effort implementation of $(D length) for any kind of -range. - -If $(D hasLength!Range), simply returns $(D range.length) without -checking $(D upTo) (when specified). - -Otherwise, walks the range through its length and returns the number -of elements seen. Performes $(BIGOH n) evaluations of $(D range.empty) -and $(D range.popFront()), where $(D n) is the effective length of $(D -range). +module std.range; -The $(D upTo) parameter is useful to "cut the losses" in case -the interest is in seeing whether the range has at least some number -of elements. If the parameter $(D upTo) is specified, stops if $(D -upTo) steps have been taken and returns $(D upTo). +public import std.range.primitives; +public import std.range.interfaces; +public import std.array; +public import std.typecons : Flag, Yes, No; -Infinite ranges are compatible, provided the parameter $(D upTo) is -specified, in which case the implementation simply returns upTo. - */ -auto walkLength(Range)(Range range) - if (isInputRange!Range && !isInfinite!Range) -{ - static if (hasLength!Range) - return range.length; - else - { - size_t result; - for ( ; !range.empty ; range.popFront() ) - ++result; - return result; - } -} -/// ditto -auto walkLength(Range)(Range range, const size_t upTo) - if (isInputRange!Range) -{ - static if (hasLength!Range) - return range.length; - else static if (isInfinite!Range) - return upTo; - else - { - size_t result; - for ( ; result < upTo && !range.empty ; range.popFront() ) - ++result; - return result; - } -} +import std.traits; +import std.typetuple; -unittest -{ - //hasLength Range - int[] a = [ 1, 2, 3 ]; - assert(walkLength(a) == 3); - assert(walkLength(a, 0) == 3); - assert(walkLength(a, 2) == 3); - assert(walkLength(a, 4) == 3); - - //Forward Range - auto b = filter!"true"([1, 2, 3, 4]); - assert(b.walkLength() == 4); - assert(b.walkLength(0) == 0); - assert(b.walkLength(2) == 2); - assert(b.walkLength(4) == 4); - assert(b.walkLength(6) == 4); - - //Infinite Range - auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1); - assert(!__traits(compiles, fibs.walkLength())); - assert(fibs.take(10).walkLength() == 10); - assert(fibs.walkLength(55) == 55); -} /** Iterates a bidirectional range backwards. The original range can be @@ -2073,16 +292,18 @@ if (isBidirectionalRange!(Unqual!Range)) /// -unittest +@safe unittest { + import std.algorithm : equal; int[] a = [ 1, 2, 3, 4, 5 ]; assert(equal(retro(a), [ 5, 4, 3, 2, 1 ][])); assert(retro(a).source is a); assert(retro(retro(a)) is a); } -unittest +@safe unittest { + import std.algorithm : equal; static assert(isBidirectionalRange!(typeof(retro("hello")))); int[] a; static assert(is(typeof(a) == typeof(retro(retro(a))))); @@ -2106,8 +327,10 @@ unittest auto r = retro(foo); } -unittest +@safe unittest { + import std.internal.test.dummyrange; + foreach(DummyType; AllDummyRanges) { static if (!isBidirectionalRange!DummyType) { static assert(!__traits(compiles, Retro!DummyType)); @@ -2157,13 +380,24 @@ unittest } } } -unittest + +@safe unittest { + import std.algorithm : equal; auto LL = iota(1L, 4L); auto r = retro(LL); assert(equal(r, [3L, 2L, 1L])); } +// Issue 12662 +@safe @nogc unittest +{ + int[3] src = [1,2,3]; + int[] data = src[]; + foreach_reverse (x; data) {} + foreach (x; data.retro) {} +} + /** Iterates range $(D r) with stride $(D n). If the range is a @@ -2173,18 +407,12 @@ the same range results in a stride with a step that is the product of the two applications. Throws: $(D Exception) if $(D n == 0). - -Example: ----- -int[] a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]; -assert(equal(stride(a, 3), [ 1, 4, 7, 10 ][])); -assert(stride(stride(a, 2), 3) == stride(a, 6)); ----- */ auto stride(Range)(Range r, size_t n) if (isInputRange!(Unqual!Range)) { import std.exception : enforce; + import std.algorithm : min; enforce(n > 0, "Stride cannot have step zero."); @@ -2388,8 +616,21 @@ if (isInputRange!(Unqual!Range)) } } +/// unittest { + import std.algorithm : equal; + + int[] a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]; + assert(equal(stride(a, 3), [ 1, 4, 7, 10 ][])); + assert(stride(stride(a, 2), 3) == stride(a, 6)); +} + +@safe unittest +{ + import std.internal.test.dummyrange; + import std.algorithm : equal; + static assert(isRandomAccessRange!(typeof(stride([1, 2, 3], 2)))); void test(size_t n, int[] input, int[] witness) { @@ -2511,8 +752,11 @@ unittest } } } -unittest + +@safe unittest { + import std.algorithm : equal; + auto LL = iota(1L, 10L); auto s = stride(LL, 3); assert(equal(s, [1L, 4L, 7L])); @@ -2529,17 +773,6 @@ length), $(D Chain) offers them as well. If only one range is offered to $(D Chain) or $(D chain), the $(D Chain) type exits the picture by aliasing itself directly to that range's type. - -Example: ----- -int[] arr1 = [ 1, 2, 3, 4 ]; -int[] arr2 = [ 5, 6 ]; -int[] arr3 = [ 7 ]; -auto s = chain(arr1, arr2, arr3); -assert(s.length == 7); -assert(s[5] == 6); -assert(equal(s, [1, 2, 3, 4, 5, 6, 7][])); ----- */ auto chain(Ranges...)(Ranges rs) if (Ranges.length > 0 && @@ -2859,8 +1092,25 @@ if (Ranges.length > 0 && } } +/// unittest { + import std.algorithm : equal; + + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] arr2 = [ 5, 6 ]; + int[] arr3 = [ 7 ]; + auto s = chain(arr1, arr2, arr3); + assert(s.length == 7); + assert(s[5] == 6); + assert(equal(s, [1, 2, 3, 4, 5, 6, 7][])); +} + +@safe unittest +{ + import std.internal.test.dummyrange; + import std.algorithm : equal; + { int[] arr1 = [ 1, 2, 3, 4 ]; int[] arr2 = [ 5, 6 ]; @@ -2951,7 +1201,7 @@ unittest } } -unittest +@safe unittest { class Foo{} immutable(Foo)[] a; @@ -2988,53 +1238,49 @@ if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs))) @property auto ref front() { - static string makeSwitch() + final switch (_current) { - string result = "switch (_current) {\n"; foreach (i, R; Rs) { - auto si = to!string(i); - result ~= "case "~si~": "~ - "assert(!source["~si~"].empty); return source["~si~"].front;\n"; + case i: + assert(!source[i].empty); + return source[i].front; } - return result ~ "default: assert(0); }"; } - - mixin(makeSwitch()); + assert(0); } void popFront() { - static string makeSwitchPopFront() + final switch (_current) { - string result = "switch (_current) {\n"; foreach (i, R; Rs) { - auto si = to!string(i); - result ~= "case "~si~": source["~si~"].popFront(); break;\n"; + case i: + source[i].popFront(); + break; } - return result ~ "default: assert(0); }"; } - static string makeSwitchIncrementCounter() + auto next = _current == (Rs.length - 1) ? 0 : (_current + 1); + final switch (next) { - string result = - "auto next = _current == Rs.length - 1 ? 0 : _current + 1;\n"~ - "switch (next) {\n"; foreach (i, R; Rs) { - auto si = to!string(i); - auto si_1 = to!string(i ? i - 1 : Rs.length - 1); - result ~= "case "~si~": "~ - "if (!source["~si~"].empty) { _current = "~si~"; return; }\n"~ - "if ("~si~" == _current) { _current = _current.max; return; }\n"~ - "goto case "~to!string((i + 1) % Rs.length)~";\n"; + case i: + if (!source[i].empty) + { + _current = i; + return; + } + if (i == _current) + { + _current = _current.max; + return; + } + goto case (i + 1) % Rs.length; } - return result ~ "default: assert(0); }"; } - - mixin(makeSwitchPopFront()); - mixin(makeSwitchIncrementCounter()); } static if (allSatisfy!(isForwardRange, staticMap!(Unqual, Rs))) @@ -3068,8 +1314,10 @@ if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs))) } /// -unittest +@safe unittest { + import std.algorithm : equal; + int[] a = [ 1, 2, 3 ]; int[] b = [ 10, 20, 30, 40 ]; auto r = roundRobin(a, b); @@ -3097,18 +1345,21 @@ if (isRandomAccessRange!(Unqual!R) && hasLength!(Unqual!R)) } /// -unittest +@safe unittest { + import std.algorithm : equal; int[] a = [ 1, 2, 3, 4, 5 ]; assert(equal(radial(a), [ 3, 4, 2, 5, 1 ])); a = [ 1, 2, 3, 4 ]; assert(equal(radial(a), [ 2, 3, 1, 4 ])); } -unittest +@safe unittest { import std.conv : text; import std.exception : enforce; + import std.algorithm : equal; + import std.internal.test.dummyrange; void test(int[] input, int[] witness) { @@ -3142,8 +1393,11 @@ unittest // immutable int[] immi = [ 1, 2 ]; // static assert(is(typeof(radial(immi)))); } -unittest + +@safe unittest { + import std.algorithm : equal; + auto LL = iota(1L, 6L); auto r = radial(LL); assert(equal(r, [3L, 4L, 2L, 5L, 1L])); @@ -3242,6 +1496,7 @@ if (isInputRange!(Unqual!Range) && { @property size_t length() { + import std.algorithm : min; return min(_maxAvailable, source.length); } @@ -3338,12 +1593,15 @@ if (isInputRange!(Unqual!R) && !isInfinite!(Unqual!R) && hasSlicing!(Unqual!R) & { // @@@BUG@@@ //return input[0 .. min(n, $)]; + import std.algorithm : min; return input[0 .. min(n, input.length)]; } /// -unittest +@safe unittest { + import std.algorithm : equal; + int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; auto s = take(arr1, 5); assert(s.length == 5); @@ -3355,6 +1613,7 @@ unittest Take!R take(R)(R input, size_t n) if (is(R T == Take!T)) { + import std.algorithm : min; return R(input.source, min(n, input._maxAvailable)); } @@ -3365,8 +1624,11 @@ if (isInputRange!(Unqual!R) && (isInfinite!(Unqual!R) || !hasSlicing!(Unqual!R) return Take!R(input, n); } -unittest +@safe unittest { + import std.internal.test.dummyrange; + import std.algorithm : equal; + int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; auto s = take(arr1, 5); assert(s.length == 5); @@ -3423,7 +1685,7 @@ unittest static assert(is(Take!(typeof(myRepeat)))); } -unittest +@safe unittest { // Check that one can declare variables of all Take types, // and that they match the return type of the corresponding @@ -3440,7 +1702,7 @@ unittest t3 = take(t2, 1); } -unittest +@safe unittest { alias R1 = typeof(repeat(1)); alias R2 = typeof(cycle([1])); @@ -3450,15 +1712,17 @@ unittest static assert(isBidirectionalRange!TR2); } -unittest //12731 +@safe unittest //12731 { auto a = repeat(1); auto s = a[1 .. 5]; s = s[1 .. 3]; } -unittest //13151 +@safe unittest //13151 { + import std.algorithm : equal; + auto r = take(repeat(1, 4), 3); assert(r.take(2).equal(repeat(1, 2))); } @@ -3552,8 +1816,10 @@ if (isInputRange!R) } /// -unittest +@safe unittest { + import std.algorithm : equal; + auto a = [ 1, 2, 3, 4, 5 ]; auto b = takeExactly(a, 3); @@ -3564,8 +1830,10 @@ unittest assert(b.back == 3); } -unittest +@safe unittest { + import std.algorithm : equal, filter; + auto a = [ 1, 2, 3, 4, 5 ]; auto b = takeExactly(a, 3); auto c = takeExactly(b, 2); @@ -3580,8 +1848,11 @@ unittest assert(equal(takeExactly(e, 3), [1, 2, 3])); } -unittest +@safe unittest { + import std.algorithm : equal; + import std.internal.test.dummyrange; + auto a = [ 1, 2, 3, 4, 5 ]; //Test that take and takeExactly are the same for ranges which define length //but aren't sliceable. @@ -3639,8 +1910,11 @@ unittest } } -unittest +@safe unittest { + import std.algorithm : equal; + import std.internal.test.dummyrange; + alias DummyType = DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward); auto te = takeExactly(DummyType(), 5); Take!DummyType t = te; @@ -3677,7 +1951,10 @@ auto takeOne(R)(R source) if (isInputRange!R) @property auto ref front() { assert(!empty); return _source.front; } void popFront() { assert(!empty); _empty = true; } void popBack() { assert(!empty); _empty = true; } - @property auto save() { return Result(_source.save, empty); } + static if (isForwardRange!(Unqual!R)) + { + @property auto save() { return Result(_source.save, empty); } + } @property auto ref back() { assert(!empty); return _source.front; } @property size_t length() const { return !empty; } alias opDollar = length; @@ -3696,7 +1973,7 @@ auto takeOne(R)(R source) if (isInputRange!R) } /// -unittest +@safe unittest { auto s = takeOne([42, 43, 44]); static assert(isRandomAccessRange!(typeof(s))); @@ -3712,6 +1989,21 @@ unittest assert(s.empty); } +unittest +{ + struct NonForwardRange + { + enum empty = false; + int front() { return 42; } + void popFront() {} + } + + static assert(!isForwardRange!NonForwardRange); + + auto s = takeOne(NonForwardRange()); + assert(s.front == 42); +} + /++ Returns an empty range which is statically known to be empty and is guaranteed to have $(D length) and be random access regardless of $(D R)'s @@ -3724,14 +2016,14 @@ auto takeNone(R)() } /// -unittest +@safe unittest { auto range = takeNone!(int[])(); assert(range.length == 0); assert(range.empty); } -unittest +@safe unittest { enum ctfe = takeNone!(int[])(); static assert(ctfe.length == 0); @@ -3769,15 +2061,18 @@ auto takeNone(R)(R range) } /// -unittest +@safe unittest { + import std.algorithm : filter; assert(takeNone([42, 27, 19]).empty); assert(takeNone("dlang.org").empty); assert(takeNone(filter!"true"([42, 27, 19])).empty); } -unittest +@safe unittest { + import std.algorithm : filter; + struct Dummy { mixin template genInput() @@ -3850,7 +2145,7 @@ unittest int[] _arr; } - import std.string : format; + import std.format : format; foreach(range; TypeTuple!([1, 2, 3, 4, 5], "hello world", @@ -3922,25 +2217,30 @@ R dropBack(R)(R range, size_t n) } /// -unittest +@safe unittest { + import std.algorithm : equal; + assert([0, 2, 1, 5, 0, 3].drop(3) == [5, 0, 3]); assert("hello world".drop(6) == "world"); assert("hello world".drop(50).empty); assert("hello world".take(6).drop(3).equal("lo ")); } -unittest +@safe unittest { + import std.algorithm : equal; + assert([0, 2, 1, 5, 0, 3].dropBack(3) == [0, 2, 1]); assert("hello world".dropBack(6) == "hello"); assert("hello world".dropBack(50).empty); assert("hello world".drop(4).dropBack(4).equal("o w")); } -unittest +@safe unittest { - import std.container : DList; + import std.algorithm : equal; + import std.container.dlist; //Remove all but the first two elements auto a = DList!int(0, 1, 9, 9, 9, 9); @@ -3948,15 +2248,18 @@ unittest assert(a[].equal(a[].take(2))); } -unittest +@safe unittest { + import std.algorithm : equal, filter; + assert(drop("", 5).empty); assert(equal(drop(filter!"true"([0, 2, 1, 5, 0, 3]), 3), [5, 0, 3])); } -unittest +@safe unittest { - import std.container : DList; + import std.algorithm : equal; + import std.container.dlist; //insert before the last two elements auto a = DList!int(0, 1, 2, 5, 6); @@ -3992,8 +2295,10 @@ R dropBackExactly(R)(R range, size_t n) } /// -unittest +@safe unittest { + import std.algorithm : equal, filterBidirectional; + auto a = [1, 2, 3]; assert(a.dropExactly(2) == [3]); assert(a.dropBackExactly(2) == [1]); @@ -4032,9 +2337,11 @@ R dropBackOne(R)(R range) } /// -unittest +@safe unittest { - import std.container : DList; + import std.algorithm : equal, filterBidirectional; + + import std.container.dlist; auto dl = DList!int(9, 1, 2, 3, 9); assert(dl[].dropOne().dropBackOne().equal([1, 2, 3])); @@ -4052,194 +2359,6 @@ unittest assert(bd.dropBackOne().equal([1, 2])); } -/** - Eagerly advances $(D r) itself (not a copy) up to $(D n) times (by - calling $(D r.popFront)). $(D popFrontN) takes $(D r) by $(D ref), - so it mutates the original range. Completes in $(BIGOH 1) steps for ranges - that support slicing and have length. - Completes in $(BIGOH n) time for all other ranges. - - Returns: - How much $(D r) was actually advanced, which may be less than $(D n) if - $(D r) did not have at least $(D n) elements. - - $(D popBackN) will behave the same but instead removes elements from - the back of the (bidirectional) range instead of the front. -*/ -size_t popFrontN(Range)(ref Range r, size_t n) - if (isInputRange!Range) -{ - static if (hasLength!Range) - n = min(n, r.length); - - static if (hasSlicing!Range && is(typeof(r = r[n .. $]))) - { - r = r[n .. $]; - } - else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. - { - r = r[n .. r.length]; - } - else - { - static if (hasLength!Range) - { - foreach (i; 0 .. n) - r.popFront(); - } - else - { - foreach (i; 0 .. n) - { - if (r.empty) return i; - r.popFront(); - } - } - } - return n; -} -/// ditto -size_t popBackN(Range)(ref Range r, size_t n) - if (isBidirectionalRange!Range) -{ - static if (hasLength!Range) - n = min(n, r.length); - - static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n]))) - { - r = r[0 .. $ - n]; - } - else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. - { - r = r[0 .. r.length - n]; - } - else - { - static if (hasLength!Range) - { - foreach (i; 0 .. n) - r.popBack(); - } - else - { - foreach (i; 0 .. n) - { - if (r.empty) return i; - r.popBack(); - } - } - } - return n; -} - -/// -unittest -{ - int[] a = [ 1, 2, 3, 4, 5 ]; - a.popFrontN(2); - assert(a == [ 3, 4, 5 ]); - a.popFrontN(7); - assert(a == [ ]); -} - -/// -unittest -{ - auto LL = iota(1L, 7L); - auto r = popFrontN(LL, 2); - assert(equal(LL, [3L, 4L, 5L, 6L])); - assert(r == 2); -} - -/// -unittest -{ - int[] a = [ 1, 2, 3, 4, 5 ]; - a.popBackN(2); - assert(a == [ 1, 2, 3 ]); - a.popBackN(7); - assert(a == [ ]); -} - -/// -unittest -{ - auto LL = iota(1L, 7L); - auto r = popBackN(LL, 2); - assert(equal(LL, [1L, 2L, 3L, 4L])); - assert(r == 2); -} - -/** - Eagerly advances $(D r) itself (not a copy) exactly $(D n) times (by - calling $(D r.popFront)). $(D popFrontExactly) takes $(D r) by $(D ref), - so it mutates the original range. Completes in $(BIGOH 1) steps for ranges - that support slicing, and have either length or are infinite. - Completes in $(BIGOH n) time for all other ranges. - - Note: Unlike $(LREF popFrontN), $(D popFrontExactly) will assume that the - range holds at least $(D n) elements. This makes $(D popFrontExactly) - faster than $(D popFrontN), but it also means that if $(D range) does - not contain at least $(D n) elements, it will attempt to call $(D popFront) - on an empty range, which is undefined behavior. So, only use - $(D popFrontExactly) when it is guaranteed that $(D range) holds at least - $(D n) elements. - - $(D popBackExactly) will behave the same but instead removes elements from - the back of the (bidirectional) range instead of the front. -*/ -void popFrontExactly(Range)(ref Range r, size_t n) - if (isInputRange!Range) -{ - static if (hasLength!Range) - assert(n <= r.length, "range is smaller than amount of items to pop"); - - static if (hasSlicing!Range && is(typeof(r = r[n .. $]))) - r = r[n .. $]; - else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. - r = r[n .. r.length]; - else - foreach (i; 0 .. n) - r.popFront(); -} -/// ditto -void popBackExactly(Range)(ref Range r, size_t n) - if (isBidirectionalRange!Range) -{ - static if (hasLength!Range) - assert(n <= r.length, "range is smaller than amount of items to pop"); - - static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n]))) - r = r[0 .. $ - n]; - else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. - r = r[0 .. r.length - n]; - else - foreach (i; 0 .. n) - r.popBack(); -} - -/// -unittest -{ - auto a = [1, 2, 3]; - a.popFrontExactly(1); - assert(a == [2, 3]); - a.popBackExactly(1); - assert(a == [2]); - - string s = "日本語"; - s.popFrontExactly(1); - assert(s == "本語"); - s.popBackExactly(1); - assert(s == "本"); - - auto bd = filterBidirectional!"true"([1, 2, 3]); - bd.popFrontExactly(1); - assert(bd.equal([2, 3])); - bd.popBackExactly(1); - assert(bd.equal([2])); -} - /** Repeats one value forever. @@ -4287,13 +2406,17 @@ public: Repeat!T repeat(T)(T value) { return Repeat!T(value); } /// -unittest +@safe unittest { + import std.algorithm : equal; + assert(equal(5.repeat().take(4), [ 5, 5, 5, 5 ])); } -unittest +@safe unittest { + import std.algorithm : equal; + auto r = repeat(5); alias R = typeof(r); static assert(isBidirectionalRange!R); @@ -4319,12 +2442,14 @@ Take!(Repeat!T) repeat(T)(T value, size_t n) } /// -unittest +@safe unittest { + import std.algorithm : equal; + assert(equal(5.repeat(4), 5.repeat().take(4))); } -unittest //12007 +@safe unittest //12007 { static class C{} Repeat!(immutable int) ri; @@ -4507,32 +2632,40 @@ struct Cycle(R) private size_t _index; nothrow: - this(ref R input, size_t index = 0) + this(ref R input, size_t index = 0) @system { _ptr = input.ptr; _index = index % R.length; } - @property ref inout(ElementType) front() inout + @property ref inout(ElementType) front() inout @safe { - return _ptr[_index]; + static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted + { + return p[idx]; + } + return trustedPtrIdx(_ptr, _index); } enum bool empty = false; - void popFront() + void popFront() @safe { ++_index; if (_index >= R.length) _index = 0; } - ref inout(ElementType) opIndex(size_t n) inout + ref inout(ElementType) opIndex(size_t n) inout @safe { - return _ptr[(n + _index) % R.length]; + static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted + { + return p[idx % R.length]; + } + return trustedPtrIdx(_ptr, n + _index); } - @property inout(Cycle) save() inout + @property inout(Cycle) save() inout @safe { return this; } @@ -4540,7 +2673,7 @@ nothrow: private static struct DollarToken {} enum opDollar = DollarToken.init; - auto opSlice(size_t i, size_t j) + auto opSlice(size_t i, size_t j) @safe in { import core.exception : RangeError; @@ -4551,10 +2684,13 @@ nothrow: return this[i .. $].takeExactly(j - i); } - inout(typeof(this)) opSlice(size_t i, DollarToken) inout + inout(typeof(this)) opSlice(size_t i, DollarToken) inout @safe { - // cast: Issue 12177 workaround - return cast(typeof(return))Cycle(*cast(R*)_ptr, _index + i); + static auto trustedCtor(typeof(_ptr) p, size_t idx) @trusted + { + return cast(inout)Cycle(*cast(R*)(p), idx); + } + return trustedCtor(_ptr, _index + i); } } @@ -4566,8 +2702,10 @@ Cycle!R cycle(R)(R input) } /// -unittest +@safe unittest { + import std.algorithm : equal; + assert(equal(take(cycle([1, 2][]), 5), [ 1, 2, 1, 2, 1 ][])); } @@ -4584,14 +2722,17 @@ Cycle!R cycle(R)(R input) return input; } -Cycle!R cycle(R)(ref R input, size_t index = 0) +Cycle!R cycle(R)(ref R input, size_t index = 0) @system if (isStaticArray!R) { return Cycle!R(input, index); } -unittest +@safe unittest { + import std.internal.test.dummyrange; + import std.algorithm : equal; + static assert(isForwardRange!(Cycle!(uint[]))); // Make sure ref is getting propagated properly. @@ -4653,8 +2794,10 @@ unittest } } -unittest // For static arrays. +@system unittest // For static arrays. { + import std.algorithm : equal; + int[3] a = [ 1, 2, 3 ]; static assert(isStaticArray!(typeof(a))); auto c = cycle(a); @@ -4669,7 +2812,7 @@ unittest // For static arrays. static assert(is(typeof(cConst[1 .. $]) == const(C))); } -unittest // For infinite ranges +@safe unittest // For infinite ranges { struct InfRange { @@ -4683,12 +2826,12 @@ unittest // For infinite ranges assert (c == i); } -unittest +@safe unittest { + import std.algorithm : equal; + int[5] arr = [0, 1, 2, 3, 4]; - auto cleS = cycle(arr); //Static auto cleD = cycle(arr[]); //Dynamic - assert(equal(cleS[5 .. 10], arr[])); assert(equal(cleD[5 .. 10], arr[])); //n is a multiple of 5 worth about 3/4 of size_t.max @@ -4698,15 +2841,35 @@ unittest //Test index overflow foreach (_ ; 0 .. 10) { - cleS = cleS[n .. $]; cleD = cleD[n .. $]; - assert(equal(cleS[5 .. 10], arr[])); assert(equal(cleD[5 .. 10], arr[])); } } -unittest +@system unittest +{ + import std.algorithm : equal; + + int[5] arr = [0, 1, 2, 3, 4]; + auto cleS = cycle(arr); //Static + assert(equal(cleS[5 .. 10], arr[])); + + //n is a multiple of 5 worth about 3/4 of size_t.max + auto n = size_t.max/4 + size_t.max/2; + n -= n % 5; + + //Test index overflow + foreach (_ ; 0 .. 10) + { + cleS = cleS[n .. $]; + assert(equal(cleS[5 .. 10], arr[])); + } +} + +@system unittest { + import std.algorithm : equal; + int[1] arr = [0]; auto cleS = cycle(arr); cleS = cleS[10 .. $]; @@ -4716,11 +2879,13 @@ unittest unittest //10845 { + import std.algorithm : equal, filter; + auto a = inputRangeObject(iota(3).filter!"true"); assert(equal(cycle(a).take(10), [0, 1, 2, 0, 1, 2, 0, 1, 2, 0])); } -unittest // 12177 +@safe unittest // 12177 { auto a = recurrence!q{a[n - 1] ~ a[n - 2]}("1", "0"); } @@ -4731,18 +2896,6 @@ private alias lengthType(R) = typeof(R.init.length.init); Iterate several ranges in lockstep. The element type is a proxy tuple that allows accessing the current element in the $(D n)th range by using $(D e[n]). - - Example: - ---- - int[] a = [ 1, 2, 3 ]; - string[] b = [ "a", "b", "c" ]; - // prints 1:a 2:b 3:c - foreach (e; zip(a, b)) - { - write(e[0], ':', e[1], ' '); - } - ---- - $(D Zip) offers the lowest range facilities of all components, e.g. it offers random access iff all ranges offer random access, and also offers mutation and swapping if all ranges offer it. Due to this, $(D @@ -4753,7 +2906,7 @@ private alias lengthType(R) = typeof(R.init.length.init); struct Zip(Ranges...) if (Ranges.length && allSatisfy!(isInputRange, Ranges)) { - import std.string : format; //for generic mixins + import std.format : format; //for generic mixins import std.typecons : Tuple; alias R = Ranges; @@ -5007,6 +3160,7 @@ struct Zip(Ranges...) return ranges[0].length; //[min|max](ranges[0].length, ranges[1].length, ...) + import std.algorithm : min, max; if (stoppingPolicy == StoppingPolicy.shortest) return mixin(q{min(%(ranges[%s].length%|, %))}.format(iota(0, R.length))); else @@ -5091,15 +3245,31 @@ auto zip(Ranges...)(Ranges ranges) } /// -unittest +pure unittest { + import std.algorithm : sort; int[] a = [ 1, 2, 3 ]; string[] b = [ "a", "b", "c" ]; - sort!("a[0] > b[0]")(zip(a, b)); + sort!((c, d) => c[0] > d[0])(zip(a, b)); assert(a == [ 3, 2, 1 ]); assert(b == [ "c", "b", "a" ]); } +/// +unittest +{ + int[] a = [ 1, 2, 3 ]; + string[] b = [ "a", "b", "c" ]; + + size_t idx = 0; + foreach (e; zip(a, b)) + { + assert(e[0] == a[idx]); + assert(e[1] == b[idx]); + ++idx; + } +} + /// Ditto auto zip(Ranges...)(StoppingPolicy sp, Ranges ranges) if (Ranges.length && allSatisfy!(isInputRange, Ranges)) @@ -5123,6 +3293,9 @@ enum StoppingPolicy unittest { + import std.internal.test.dummyrange; + import std.algorithm : swap, sort, filter, equal, map; + import std.exception : assertThrown, assertNotThrown; import std.typecons : tuple; @@ -5234,8 +3407,10 @@ unittest +/ } -unittest +pure unittest { + import std.algorithm : sort; + auto a = [5,4,3,2,1]; auto b = [3,1,2,5,6]; auto z = zip(a, b); @@ -5249,6 +3424,8 @@ unittest @safe pure unittest { import std.typecons : tuple; + import std.algorithm : equal; + auto LL = iota(1L, 1000L); auto z = zip(LL, [4]); @@ -5291,7 +3468,7 @@ unittest */ private string lockstepMixin(Ranges...)(bool withIndex) { - import std.string : format, outdent; + import std.format : format; string[] params; string[] emptyChecks; @@ -5340,7 +3517,7 @@ private string lockstepMixin(Ranges...)(bool withIndex) }, params.join(", "), withIndex ? "size_t index = 0;" : "", emptyChecks.join(" && "), dgArgs.join(", "), popFronts.join("\n "), - withIndex ? "index++;" : "").outdent(); + withIndex ? "index++;" : ""); } /** @@ -5428,6 +3605,7 @@ unittest unittest { import std.conv : to; + import std.algorithm : filter; // The filters are to make these the lowest common forward denominator ranges, // i.e. w/o ref return, random access, length, etc. @@ -5525,6 +3703,15 @@ Fibonacci sequence, there are two initial values (and therefore a state size of 2) because computing the next Fibonacci value needs the past two values. +The signature of this function should be: +---- +auto fun(R)(R state, size_t n) +---- +where $(D n) will be the index of the current value, and $(D state) will be an +opaque state vector that can be indexed with array-indexing notation +$(D state[i]), where valid values of $(D i) range from $(D (n - 1)) to +$(D (n - State.length)). + If the function is passed in string form, the state has name $(D "a") and the zero-based index in the recurrence has name $(D "n"). The given string must return the desired value for $(D a[n]) given $(D a[n @@ -5532,16 +3719,6 @@ given string must return the desired value for $(D a[n]) given $(D a[n state size is dictated by the number of arguments passed to the call to $(D recurrence). The $(D Recurrence) struct itself takes care of managing the recurrence's state and shifting it appropriately. - -Example: ----- -// a[0] = 1, a[1] = 1, and compute a[n+1] = a[n-1] + a[n] -auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1); -// print the first 10 Fibonacci numbers -foreach (e; take(fib, 10)) { writeln(e); } -// print the first 10 factorials -foreach (e; take(recurrence!("a[n-1] * n")(1), 10)) { writeln(e); } ----- */ struct Recurrence(alias fun, StateType, size_t stateSize) { @@ -5554,11 +3731,15 @@ struct Recurrence(alias fun, StateType, size_t stateSize) void popFront() { + static auto trustedCycle(ref typeof(_state) s) @trusted + { + return cycle(s); + } // The cast here is reasonable because fun may cause integer // promotion, but needs to return a StateType to make its operation // closed. Therefore, we have no other choice. _state[_n % stateSize] = cast(StateType) binaryFun!(fun, "a", "n")( - cycle(_state), _n + stateSize); + trustedCycle(_state), _n + stateSize); ++_n; } @@ -5575,6 +3756,31 @@ struct Recurrence(alias fun, StateType, size_t stateSize) enum bool empty = false; } +/// +@safe unittest +{ + import std.algorithm : equal; + + // The Fibonacci numbers, using function in string form: + // a[0] = 1, a[1] = 1, and compute a[n+1] = a[n-1] + a[n] + auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1); + assert(fib.take(10).equal([1, 1, 2, 3, 5, 8, 13, 21, 34, 55])); + + // The factorials, using function in lambda form: + auto fac = recurrence!((a,n) => a[n-1] * n)(1); + assert(take(fac, 10).equal([ + 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 + ])); + + // The triangular numbers, using function in explicit form: + static size_t genTriangular(R)(R state, size_t n) + { + return state[n-1] + n; + } + auto tri = recurrence!genTriangular(0); + assert(take(tri, 10).equal([0, 1, 3, 6, 10, 15, 21, 28, 36, 45])); +} + /// Ditto Recurrence!(fun, CommonType!(State), State.length) recurrence(alias fun, State...)(State initial) @@ -5587,8 +3793,10 @@ recurrence(alias fun, State...)(State initial) return typeof(return)(state); } -unittest +@safe unittest { + import std.algorithm : equal; + auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1); static assert(isForwardRange!(typeof(fib))); @@ -5627,7 +3835,6 @@ private: alias ElementType = typeof(compute(State.init, cast(size_t) 1)); State _state; size_t _n; - ElementType _cache; static struct DollarToken{} @@ -5636,22 +3843,16 @@ public: { _state = initial; _n = n; - _cache = compute(_state, _n); } @property ElementType front() { - return _cache; - } - - ElementType moveFront() - { - return move(this._cache); + return compute(_state, _n); } void popFront() { - _cache = compute(_state, ++_n); + ++_n; } enum opDollar = DollarToken(); @@ -5689,8 +3890,8 @@ auto sequence(alias fun, State...)(State args) return Return(tuple(args)); } -/// -unittest +/// Odd numbers, using function in string form: +@safe unittest { auto odds = sequence!("a[0] + n * a[1]")(1, 2); assert(odds.front == 1); @@ -5700,7 +3901,43 @@ unittest assert(odds.front == 5); } -unittest +/// Triangular numbers, using function in lambda form: +@safe unittest +{ + auto tri = sequence!((a,n) => n*(n+1)/2)(); + + // Note random access + assert(tri[0] == 0); + assert(tri[3] == 6); + assert(tri[1] == 1); + assert(tri[4] == 10); + assert(tri[2] == 3); +} + +/// Fibonacci numbers, using function in explicit form: +@safe unittest +{ + import std.math : pow, round, sqrt; + static ulong computeFib(S)(S state, size_t n) + { + // Binet's formula + return cast(ulong)(round((pow(state[0], n+1) - pow(state[1], n+1)) / + state[2])); + } + auto fib = sequence!computeFib( + (1.0 + sqrt(5.0)) / 2.0, // Golden Ratio + (1.0 - sqrt(5.0)) / 2.0, // Conjugate of Golden Ratio + sqrt(5.0)); + + // Note random access with [] operator + assert(fib[1] == 1); + assert(fib[4] == 5); + assert(fib[3] == 3); + assert(fib[2] == 2); + assert(fib[9] == 55); +} + +@safe unittest { import std.typecons : Tuple, tuple; auto y = Sequence!("a[0] + n * a[1]", Tuple!(int, int))(tuple(0, 4)); @@ -5720,8 +3957,10 @@ unittest } } -unittest +@safe unittest { + import std.algorithm : equal; + auto odds = sequence!("a[0] + n * a[1]")(1, 2); static assert(hasSlicing!(typeof(odds))); @@ -5742,13 +3981,34 @@ unittest assert(equal(odds.take(3), [21, 23, 25])); } +// Issue 5036 +unittest +{ + auto s = sequence!((a, n) => new int)(0); + assert(s.front != s.front); // no caching +} + /** - Returns a range that goes through the numbers $(D begin), $(D begin + - step), $(D begin + 2 * step), $(D ...), up to and excluding $(D - end). The range offered is a random access range. The two-arguments - version has $(D step = 1). If $(D begin < end && step < 0) or $(D - begin > end && step > 0) or $(D begin == end), then an empty range is - returned. + Construct a range of values that span the given starting and stopping + values. + + Params: + begin = The starting value. + end = The value that serves as the stopping criterion. This value is not + included in the range. + step = The value to add to the current value at each iteration. + + Returns: + A range that goes through the numbers $(D begin), $(D begin + step), + $(D begin + 2 * step), $(D ...), up to and excluding $(D end). + + The two-argument overloads have $(D step = 1). If $(D begin < end && step < + 0) or $(D begin > end && step > 0) or $(D begin == end), then an empty range + is returned. + + For built-in types, the range returned is a random access range. For + user-defined types that support $(D ++), the range is an input + range. Throws: $(D Exception) if $(D begin != end && step == 0), an exception is @@ -5920,6 +4180,7 @@ auto iota(E)(E end) return iota(begin, end); } +/// Ditto // Specialization for floating-point types auto iota(B, E, S)(B begin, E end, S step) if (isFloatingPoint!(CommonType!(B, E, S))) @@ -6003,22 +4264,33 @@ if (isFloatingPoint!(CommonType!(B, E, S))) return Result(begin, end, step); } -/// +/// +@safe unittest +{ + import std.algorithm : equal; + import std.math : approxEqual; + + auto r = iota(0, 10, 1); + assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][])); + r = iota(0, 11, 3); + assert(equal(r, [0, 3, 6, 9][])); + assert(r[2] == 6); + auto rf = iota(0.0, 0.5, 0.1); + assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4])); +} + unittest { - import std.math : approxEqual; - auto r = iota(0, 10, 1); - assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][])); - r = iota(0, 11, 3); - assert(equal(r, [0, 3, 6, 9][])); - assert(r[2] == 6); - auto rf = iota(0.0, 0.5, 0.1); - assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4])); + int[] a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + auto r1 = iota(a.ptr, a.ptr + a.length, 1); + assert(r1.front == a.ptr); + assert(r1.back == a.ptr + a.length - 1); } -unittest +@safe unittest { import std.math : approxEqual, nextUp, nextDown; + import std.algorithm : count, equal; static assert(hasLength!(typeof(iota(0, 2)))); auto r = iota(0, 10, 1); @@ -6063,11 +4335,6 @@ unittest rSlice = r[1..3]; assert(equal(rSlice, [3, 6])); - int[] a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - auto r1 = iota(a.ptr, a.ptr + a.length, 1); - assert(r1.front == a.ptr); - assert(r1.back == a.ptr + a.length - 1); - auto rf = iota(0.0, 0.5, 0.1); assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4][])); assert(rf.length == 5); @@ -6140,13 +4407,14 @@ unittest } } -unittest +@safe unittest { + import std.algorithm : copy; auto idx = new size_t[100]; copy(iota(0, idx.length), idx); } -unittest +@safe unittest { foreach(range; TypeTuple!(iota(2, 27, 4), iota(3, 9), @@ -6162,7 +4430,10 @@ unittest const s2 = cRange[0 .. 3]; const l = cRange.length; } +} +unittest +{ //The ptr stuff can't be done at compile time, so we unfortunately end //up with some code duplication here. auto arr = [0, 5, 3, 5, 5, 7, 9, 2, 0, 42, 7, 6]; @@ -6190,6 +4461,91 @@ unittest } } +/* Generic overload that handles arbitrary types that support arithmetic + * operations. + */ +/// ditto +auto iota(B, E)(B begin, E end) + if (!isIntegral!(CommonType!(B, E)) && + !isFloatingPoint!(CommonType!(B, E)) && + !isPointer!(CommonType!(B, E)) && + is(typeof((ref B b) { ++b; })) && + (is(typeof(B.init < E.init)) || is(typeof(B.init == E.init))) ) +{ + static struct Result + { + B current; + E end; + + @property bool empty() + { + static if (is(typeof(B.init < E.init))) + return !(current < end); + else static if (is(typeof(B.init != E.init))) + return current == end; + else + static assert(0); + } + @property auto front() { return current; } + void popFront() + { + assert(!empty); + ++current; + } + } + return Result(begin, end); +} + +/** +User-defined types such as $(XREF bigint, BigInt) are also supported, as long +as they can be incremented with $(D ++) and compared with $(D <) or $(D ==). +*/ +// Issue 6447 +unittest +{ + import std.algorithm.comparison : equal; + import std.bigint; + + auto s = BigInt(1_000_000_000_000); + auto e = BigInt(1_000_000_000_003); + auto r = iota(s, e); + assert(r.equal([ + BigInt(1_000_000_000_000), + BigInt(1_000_000_000_001), + BigInt(1_000_000_000_002) + ])); +} + +unittest +{ + import std.algorithm.comparison : equal; + + // Test iota() for a type that only supports ++ and != but does not have + // '<'-ordering. + struct Cyclic(int wrapAround) + { + int current; + + this(int start) { current = start % wrapAround; } + + bool opEquals(Cyclic c) { return current == c.current; } + bool opEquals(int i) { return current == i; } + void opUnary(string op)() if (op == "++") + { + current = (current + 1) % wrapAround; + } + } + alias Cycle5 = Cyclic!5; + + // Easy case + auto i1 = iota(Cycle5(1), Cycle5(4)); + assert(i1.equal([1, 2, 3])); + + // Wraparound case + auto i2 = iota(Cycle5(3), Cycle5(2)); + assert(i2.equal([3, 4, 0, 1 ])); +} + /** Options for the $(LREF FrontTransversal) and $(LREF Transversal) ranges (below). @@ -6425,16 +4781,21 @@ FrontTransversal!(RangeOfRanges, opt) frontTransversal( } /// -unittest +@safe unittest { - int[][] x = new int[][2]; - x[0] = [1, 2]; - x[1] = [3, 4]; - auto ror = frontTransversal(x); - assert(equal(ror, [ 1, 3 ][])); + import std.algorithm : equal; + int[][] x = new int[][2]; + x[0] = [1, 2]; + x[1] = [3, 4]; + auto ror = frontTransversal(x); + assert(equal(ror, [ 1, 3 ][])); } -unittest { +@safe unittest +{ + import std.internal.test.dummyrange; + import std.algorithm : equal; + static assert(is(FrontTransversal!(immutable int[][]))); foreach(DummyType; AllDummyRanges) { @@ -6716,17 +5077,20 @@ Transversal!(RangeOfRanges, opt) transversal } /// -unittest +@safe unittest { - int[][] x = new int[][2]; - x[0] = [1, 2]; - x[1] = [3, 4]; - auto ror = transversal(x, 1); - assert(equal(ror, [ 2, 4 ][])); + import std.algorithm : equal; + int[][] x = new int[][2]; + x[0] = [1, 2]; + x[1] = [3, 4]; + auto ror = transversal(x, 1); + assert(equal(ror, [ 2, 4 ][])); } -unittest +@safe unittest { + import std.internal.test.dummyrange; + int[][] x = new int[][2]; x[0] = [ 1, 2 ]; x[1] = [3, 4]; @@ -6796,7 +5160,10 @@ struct Transposed(RangeOfRanges) @property auto front() { - return map!"a.front"(_input.save); + import std.algorithm : filter, map; + return _input.save + .filter!(a => !a.empty) + .map!(a => a.front); } void popFront() @@ -6806,8 +5173,11 @@ struct Transposed(RangeOfRanges) while (!r.empty) { auto e = r.front; - e.popFront(); - r.front = e; + if (!e.empty) + { + e.popFront(); + r.front = e; + } r.popFront(); } @@ -6839,13 +5209,25 @@ private: RangeOfRanges _input; } -unittest +@safe unittest { // Boundary case: transpose of empty range should be empty int[][] ror = []; assert(transposed(ror).empty); } +// Issue 9507 +unittest +{ + import std.algorithm : equal; + + auto r = [[1,2], [3], [4,5], [], [6]]; + assert(r.transposed.equal!equal([ + [1, 3, 4, 6], + [2, 5] + ])); +} + /** Given a range of ranges, returns a range of ranges where the $(I i)'th subrange contains the $(I i)'th elements of the original subranges. @@ -6859,8 +5241,9 @@ Transposed!RangeOfRanges transposed(RangeOfRanges)(RangeOfRanges rr) } /// Example -unittest +@safe unittest { + import std.algorithm : equal; int[][] ror = [ [1, 2, 3], [4, 5, 6] @@ -6874,7 +5257,7 @@ unittest } /// -unittest +@safe unittest { int[][] x = new int[][2]; x[0] = [1, 2]; @@ -6890,8 +5273,9 @@ unittest } // Issue 8764 -unittest +@safe unittest { + import std.algorithm : equal; ulong[1] t0 = [ 123 ]; assert(!hasAssignableElements!(typeof(t0[].chunks(1)))); @@ -7115,8 +5499,9 @@ Indexed!(Source, Indices) indexed(Source, Indices)(Source source, Indices indice } /// -unittest +@safe unittest { + import std.algorithm : equal; auto source = [1, 2, 3, 4, 5]; auto indices = [4, 3, 1, 2, 0, 4]; auto ind = indexed(source, indices); @@ -7124,7 +5509,7 @@ unittest assert(equal(retro(ind), [5, 1, 3, 2, 4, 5])); } -unittest +@safe unittest { { auto ind = indexed([1, 2, 3, 4, 5], [1, 3, 4]); @@ -7142,8 +5527,10 @@ unittest assert(ind[5] == 6); } -unittest +@safe unittest { + import std.internal.test.dummyrange; + foreach(DummyType; AllDummyRanges) { auto d = DummyType.init; @@ -7236,6 +5623,7 @@ struct Chunks(Source) return _source[start .. end]; else { + import std.algorithm : min; immutable len = _source.length; assert(start < len, "chunks index out of bounds"); return _source[start .. min(end, len)]; @@ -7246,6 +5634,7 @@ struct Chunks(Source) static if (hasLength!Source) typeof(this) opSlice(size_t lower, size_t upper) { + import std.algorithm : min; assert(lower <= upper && upper <= length, "chunks slicing index out of bounds"); immutable len = _source.length; return chunks(_source[min(lower * _chunkSize, len) .. min(upper * _chunkSize, len)], _chunkSize); @@ -7310,6 +5699,7 @@ struct Chunks(Source) } typeof(this) opSlice(size_t lower, DollarToken) { + import std.algorithm : min; assert(lower <= length, "chunks slicing index out of bounds"); static if (hasSliceToEnd) return chunks(_source[min(lower * _chunkSize, _source.length) .. $], _chunkSize); @@ -7364,8 +5754,9 @@ if (isForwardRange!Source) } /// -unittest +@safe unittest { + import std.algorithm : equal; auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; auto chunks = chunks(source, 4); assert(chunks[0] == [1, 2, 3, 4]); @@ -7377,7 +5768,7 @@ unittest assert(equal(retro(array(chunks)), array(retro(chunks)))); } -unittest +@safe unittest { auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; auto chunks = chunks(source, 4); @@ -7392,8 +5783,10 @@ unittest static assert(isRandomAccessRange!(typeof(chunks))); } -unittest +@safe unittest { + import std.algorithm : equal; + //Extra toying with slicing and indexing. auto chunks1 = [0, 0, 1, 1, 2, 2, 3, 3, 4].chunks(2); auto chunks2 = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4].chunks(2); @@ -7427,6 +5820,8 @@ unittest unittest { + import std.algorithm : equal, filter; + //ForwardRange auto r = filter!"true"([1, 2, 3, 4, 5]).chunks(2); assert(equal!"equal(a, b)"(r, [[1, 2], [3, 4], [5]])); @@ -7652,9 +6047,11 @@ auto only(Values...)(auto ref Values values) } /// -unittest +@safe unittest { + import std.algorithm; import std.uni; + assert(equal(only('♡'), "♡")); assert([1, 2, 3, 4].findSplitBefore(only(3))[0] == [1, 2]); @@ -7664,7 +6061,7 @@ unittest assert(filter!isUpper(title).map!only().join(".") == "T.D.P.L"); } -version(unittest) +unittest { // Verify that the same common type and same arity // results in the same template instantiation @@ -7676,8 +6073,10 @@ version(unittest) } // Tests the zero-element result -unittest +@safe unittest { + import std.algorithm : equal; + auto emptyRange = only(); alias EmptyRange = typeof(emptyRange); @@ -7696,8 +6095,9 @@ unittest } // Tests the single-element result -unittest +@safe unittest { + import std.algorithm : equal; import std.typecons : tuple; foreach (x; tuple(1, '1', 1.0, "1", [1])) { @@ -7743,643 +6143,452 @@ unittest assert(imm[0] == 1); } -// Tests multiple-element results -unittest -{ - static assert(!__traits(compiles, only(1, "1"))); - - auto nums = only!(byte, uint, long)(1, 2, 3); - static assert(is(ElementType!(typeof(nums)) == long)); - assert(nums.length == 3); - - foreach(i; 0 .. 3) - assert(nums[i] == i + 1); - - auto saved = nums.save; - - foreach(i; 1 .. 4) - { - assert(nums.front == nums[0]); - assert(nums.front == i); - nums.popFront(); - assert(nums.length == 3 - i); - } - - assert(nums.empty); - - assert(saved.equal(only(1, 2, 3))); - assert(saved.equal(saved[])); - assert(saved[0 .. 1].equal(only(1))); - assert(saved[0 .. 2].equal(only(1, 2))); - assert(saved[0 .. 3].equal(saved)); - assert(saved[1 .. 3].equal(only(2, 3))); - assert(saved[2 .. 3].equal(only(3))); - assert(saved[0 .. 0].empty); - assert(saved[3 .. 3].empty); - - alias data = TypeTuple!("one", "two", "three", "four"); - static joined = - ["one two", "one two three", "one two three four"]; - string[] joinedRange = joined; - - foreach(argCount; TypeTuple!(2, 3, 4)) - { - auto values = only(data[0 .. argCount]); - alias Values = typeof(values); - static assert(is(ElementType!Values == string)); - static assert(isInputRange!Values); - static assert(isForwardRange!Values); - static assert(isBidirectionalRange!Values); - static assert(isRandomAccessRange!Values); - static assert(hasSlicing!Values); - static assert(hasLength!Values); - - assert(values.length == argCount); - assert(values[0 .. $].equal(values[0 .. values.length])); - assert(values.joiner(" ").equal(joinedRange.front)); - joinedRange.popFront(); - } - - assert(saved.retro.equal(only(3, 2, 1))); - assert(saved.length == 3); - - assert(saved.back == 3); - saved.popBack(); - assert(saved.length == 2); - assert(saved.back == 2); - - assert(saved.front == 1); - saved.popFront(); - assert(saved.length == 1); - assert(saved.front == 2); - - saved.popBack(); - assert(saved.empty); - - auto imm = only!(immutable int, immutable int)(42, 24); - alias Imm = typeof(imm); - static assert(is(ElementType!Imm == immutable(int))); - assert(!imm.empty); - assert(imm.init.empty); // Issue 13441 - assert(imm.front == 42); - imm.popFront(); - assert(imm.front == 24); - imm.popFront(); - assert(imm.empty); - - static struct Test { int* a; } - immutable(Test) test; - cast(void)only(test, test); // Works with mutable indirection -} - -/** - Moves the front of $(D r) out and returns it. Leaves $(D r.front) in a - destroyable state that does not allocate any resources (usually equal - to its $(D .init) value). -*/ -ElementType!R moveFront(R)(R r) -{ - static if (is(typeof(&r.moveFront))) { - return r.moveFront(); - } else static if (!hasElaborateCopyConstructor!(ElementType!R)) { - return r.front; - } else static if (is(typeof(&(r.front())) == ElementType!R*)) { - return move(r.front); - } else { - static assert(0, - "Cannot move front of a range with a postblit and an rvalue front."); - } -} - -/// -unittest -{ - auto a = [ 1, 2, 3 ]; - assert(moveFront(a) == 1); - - // define a perfunctory input range - struct InputRange - { - @property bool empty() { return false; } - @property int front() { return 42; } - void popFront() {} - int moveFront() { return 43; } - } - InputRange r; - assert(moveFront(r) == 43); -} - -unittest -{ - struct R - { - @property ref int front() { static int x = 42; return x; } - this(this){} - } - R r; - assert(moveFront(r) == 42); -} - -/** - Moves the back of $(D r) out and returns it. Leaves $(D r.back) in a - destroyable state that does not allocate any resources (usually equal - to its $(D .init) value). -*/ -ElementType!R moveBack(R)(R r) -{ - static if (is(typeof(&r.moveBack))) { - return r.moveBack(); - } else static if (!hasElaborateCopyConstructor!(ElementType!R)) { - return r.back; - } else static if (is(typeof(&(r.back())) == ElementType!R*)) { - return move(r.back); - } else { - static assert(0, - "Cannot move back of a range with a postblit and an rvalue back."); - } -} - -/// -unittest -{ - struct TestRange - { - int payload = 5; - @property bool empty() { return false; } - @property TestRange save() { return this; } - @property ref int front() { return payload; } - @property ref int back() { return payload; } - void popFront() { } - void popBack() { } - } - static assert(isBidirectionalRange!TestRange); - TestRange r; - auto x = moveBack(r); - assert(x == 5); -} - -/** - Moves element at index $(D i) of $(D r) out and returns it. Leaves $(D - r.front) in a destroyable state that does not allocate any resources - (usually equal to its $(D .init) value). -*/ -ElementType!R moveAt(R, I)(R r, I i) if (isIntegral!I) -{ - static if (is(typeof(&r.moveAt))) { - return r.moveAt(i); - } else static if (!hasElaborateCopyConstructor!(ElementType!(R))) { - return r[i]; - } else static if (is(typeof(&r[i]) == ElementType!R*)) { - return move(r[i]); - } else { - static assert(0, - "Cannot move element of a range with a postblit and rvalue elements."); - } -} - -/// -unittest -{ - auto a = [1,2,3,4]; - foreach(idx, it; a) - { - assert(it == moveAt(a, idx)); - } -} - -unittest -{ - foreach(DummyType; AllDummyRanges) { - auto d = DummyType.init; - assert(moveFront(d) == 1); - - static if (isBidirectionalRange!DummyType) { - assert(moveBack(d) == 10); - } - - static if (isRandomAccessRange!DummyType) { - assert(moveAt(d, 2) == 3); - } - } -} - -/**These interfaces are intended to provide virtual function-based wrappers - * around input ranges with element type E. This is useful where a well-defined - * binary interface is required, such as when a DLL function or virtual function - * needs to accept a generic range as a parameter. Note that - * $(LREF isInputRange) and friends check for conformance to structural - * interfaces, not for implementation of these $(D interface) types. - * - * Examples: - * --- - * void useRange(InputRange!int range) { - * // Function body. - * } - * - * // Create a range type. - * auto squares = map!"a * a"(iota(10)); - * - * // Wrap it in an interface. - * auto squaresWrapped = inputRangeObject(squares); - * - * // Use it. - * useRange(squaresWrapped); - * --- - * - * Limitations: - * - * These interfaces are not capable of forwarding $(D ref) access to elements. - * - * Infiniteness of the wrapped range is not propagated. - * - * Length is not propagated in the case of non-random access ranges. - * - * See_Also: - * $(LREF inputRangeObject) - */ -interface InputRange(E) { - /// - @property E front(); - - /// - E moveFront(); - - /// - void popFront(); - - /// - @property bool empty(); - - /* Measurements of the benefits of using opApply instead of range primitives - * for foreach, using timings for iterating over an iota(100_000_000) range - * with an empty loop body, using the same hardware in each case: - * - * Bare Iota struct, range primitives: 278 milliseconds - * InputRangeObject, opApply: 436 milliseconds (1.57x penalty) - * InputRangeObject, range primitives: 877 milliseconds (3.15x penalty) - */ - - /**$(D foreach) iteration uses opApply, since one delegate call per loop - * iteration is faster than three virtual function calls. - */ - int opApply(int delegate(E)); - - /// Ditto - int opApply(int delegate(size_t, E)); - -} - -/**Interface for a forward range of type $(D E).*/ -interface ForwardRange(E) : InputRange!E { - /// - @property ForwardRange!E save(); -} - -/**Interface for a bidirectional range of type $(D E).*/ -interface BidirectionalRange(E) : ForwardRange!(E) { - /// - @property BidirectionalRange!E save(); +// Tests multiple-element results +@safe unittest +{ + import std.algorithm : equal, joiner; + static assert(!__traits(compiles, only(1, "1"))); - /// - @property E back(); + auto nums = only!(byte, uint, long)(1, 2, 3); + static assert(is(ElementType!(typeof(nums)) == long)); + assert(nums.length == 3); - /// - E moveBack(); + foreach(i; 0 .. 3) + assert(nums[i] == i + 1); - /// - void popBack(); -} + auto saved = nums.save; -/**Interface for a finite random access range of type $(D E).*/ -interface RandomAccessFinite(E) : BidirectionalRange!(E) { - /// - @property RandomAccessFinite!E save(); + foreach(i; 1 .. 4) + { + assert(nums.front == nums[0]); + assert(nums.front == i); + nums.popFront(); + assert(nums.length == 3 - i); + } - /// - E opIndex(size_t); + assert(nums.empty); - /// - E moveAt(size_t); + assert(saved.equal(only(1, 2, 3))); + assert(saved.equal(saved[])); + assert(saved[0 .. 1].equal(only(1))); + assert(saved[0 .. 2].equal(only(1, 2))); + assert(saved[0 .. 3].equal(saved)); + assert(saved[1 .. 3].equal(only(2, 3))); + assert(saved[2 .. 3].equal(only(3))); + assert(saved[0 .. 0].empty); + assert(saved[3 .. 3].empty); - /// - @property size_t length(); + alias data = TypeTuple!("one", "two", "three", "four"); + static joined = + ["one two", "one two three", "one two three four"]; + string[] joinedRange = joined; - /// - alias opDollar = length; + foreach(argCount; TypeTuple!(2, 3, 4)) + { + auto values = only(data[0 .. argCount]); + alias Values = typeof(values); + static assert(is(ElementType!Values == string)); + static assert(isInputRange!Values); + static assert(isForwardRange!Values); + static assert(isBidirectionalRange!Values); + static assert(isRandomAccessRange!Values); + static assert(hasSlicing!Values); + static assert(hasLength!Values); - // Can't support slicing until issues with requiring slicing for all - // finite random access ranges are fully resolved. - version(none) { - /// - RandomAccessFinite!E opSlice(size_t, size_t); + assert(values.length == argCount); + assert(values[0 .. $].equal(values[0 .. values.length])); + assert(values.joiner(" ").equal(joinedRange.front)); + joinedRange.popFront(); } -} -/**Interface for an infinite random access range of type $(D E).*/ -interface RandomAccessInfinite(E) : ForwardRange!E { - /// - E moveAt(size_t); + assert(saved.retro.equal(only(3, 2, 1))); + assert(saved.length == 3); - /// - @property RandomAccessInfinite!E save(); + assert(saved.back == 3); + saved.popBack(); + assert(saved.length == 2); + assert(saved.back == 2); - /// - E opIndex(size_t); -} + assert(saved.front == 1); + saved.popFront(); + assert(saved.length == 1); + assert(saved.front == 2); -/**Adds assignable elements to InputRange.*/ -interface InputAssignable(E) : InputRange!E { - /// - @property void front(E newVal); -} + saved.popBack(); + assert(saved.empty); + + auto imm = only!(immutable int, immutable int)(42, 24); + alias Imm = typeof(imm); + static assert(is(ElementType!Imm == immutable(int))); + assert(!imm.empty); + assert(imm.init.empty); // Issue 13441 + assert(imm.front == 42); + imm.popFront(); + assert(imm.front == 24); + imm.popFront(); + assert(imm.empty); -/**Adds assignable elements to ForwardRange.*/ -interface ForwardAssignable(E) : InputAssignable!E, ForwardRange!E { - /// - @property ForwardAssignable!E save(); + static struct Test { int* a; } + immutable(Test) test; + cast(void)only(test, test); // Works with mutable indirection } -/**Adds assignable elements to BidirectionalRange.*/ -interface BidirectionalAssignable(E) : ForwardAssignable!E, BidirectionalRange!E { - /// - @property BidirectionalAssignable!E save(); +/** +Iterate over $(D range) with an attached index variable. - /// - @property void back(E newVal); -} +Each element is a $(XREF typecons, Tuple) containing the index +and the element, in that order, where the index member is named $(D index) +and the element member is named $(D value). -/**Adds assignable elements to RandomAccessFinite.*/ -interface RandomFiniteAssignable(E) : RandomAccessFinite!E, BidirectionalAssignable!E { - /// - @property RandomFiniteAssignable!E save(); +The index starts at $(D start) and is incremented by one on every iteration. - /// - void opIndexAssign(E val, size_t index); -} +Bidirectionality is propagated only if $(D range) has length. -/**Interface for an output range of type $(D E). Usage is similar to the - * $(D InputRange) interface and descendants.*/ -interface OutputRange(E) { - /// - void put(E); -} +Overflow: +If $(D range) has length, then it is an error to pass a value for $(D start) +so that $(D start + range.length) is bigger than $(D Enumerator.max), thus it is +ensured that overflow cannot happen. -// CTFE function that generates mixin code for one put() method for each -// type E. -private string putMethods(E...)() -{ - import std.conv : to; +If $(D range) does not have length, and $(D popFront) is called when +$(D front.index == Enumerator.max), the index will overflow and +continue from $(D Enumerator.min). - string ret; +Examples: +Useful for using $(D foreach) with an index loop variable: +---- + import std.stdio : stdin, stdout; + import std.range : enumerate; - foreach (ti, Unused; E) + foreach (lineNum, line; stdin.byLine().enumerate(1)) + stdout.writefln("line #%s: %s", lineNum, line); +---- +*/ +auto enumerate(Enumerator = size_t, Range)(Range range, Enumerator start = 0) + if (isIntegral!Enumerator && isInputRange!Range) +in +{ + static if (hasLength!Range) { - ret ~= "void put(E[" ~ to!string(ti) ~ "] e) { .put(_range, e); }"; - } + // TODO: core.checkedint supports mixed signedness yet? + import core.checkedint : adds, addu; + import std.conv : ConvException, to; + import core.exception : RangeError; - return ret; -} + alias LengthType = typeof(range.length); + bool overflow; + static if(isSigned!Enumerator && isSigned!LengthType) + auto result = adds(start, range.length, overflow); + else static if(isSigned!Enumerator) + { + Largest!(Enumerator, Signed!LengthType) signedLength; + try signedLength = to!(typeof(signedLength))(range.length); + catch(ConvException) + overflow = true; + catch(Exception) + assert(false); -/**Implements the $(D OutputRange) interface for all types E and wraps the - * $(D put) method for each type $(D E) in a virtual function. - */ -class OutputRangeObject(R, E...) : staticMap!(OutputRange, E) { - // @BUG 4689: There should be constraints on this template class, but - // DMD won't let me put them in. - private R _range; + auto result = adds(start, signedLength, overflow); + } + else + { + static if(isSigned!LengthType) + assert(range.length >= 0); + auto result = addu(start, range.length, overflow); + } - this(R range) { - this._range = range; + if (overflow || result > Enumerator.max) + throw new RangeError("overflow in `start + range.length`"); } - - mixin(putMethods!E()); } +body +{ + // TODO: Relax isIntegral!Enumerator to allow user-defined integral types + static struct Result + { + import std.typecons : Tuple; + private: + alias ElemType = Tuple!(Enumerator, "index", ElementType!Range, "value"); + Range range; + Enumerator index; -/**Returns the interface type that best matches $(D R).*/ -template MostDerivedInputRange(R) if (isInputRange!(Unqual!R)) { - private alias E = ElementType!R; - - static if (isRandomAccessRange!R) { - static if (isInfinite!R) { - alias MostDerivedInputRange = RandomAccessInfinite!E; - } else static if (hasAssignableElements!R) { - alias MostDerivedInputRange = RandomFiniteAssignable!E; - } else { - alias MostDerivedInputRange = RandomAccessFinite!E; - } - } else static if (isBidirectionalRange!R) { - static if (hasAssignableElements!R) { - alias MostDerivedInputRange = BidirectionalAssignable!E; - } else { - alias MostDerivedInputRange = BidirectionalRange!E; - } - } else static if (isForwardRange!R) { - static if (hasAssignableElements!R) { - alias MostDerivedInputRange = ForwardAssignable!E; - } else { - alias MostDerivedInputRange = ForwardRange!E; - } - } else { - static if (hasAssignableElements!R) { - alias MostDerivedInputRange = InputAssignable!E; - } else { - alias MostDerivedInputRange = InputRange!E; + public: + ElemType front() @property + { + assert(!range.empty); + return typeof(return)(index, range.front); } - } -} -/**Implements the most derived interface that $(D R) works with and wraps - * all relevant range primitives in virtual functions. If $(D R) is already - * derived from the $(D InputRange) interface, aliases itself away. - */ -template InputRangeObject(R) if (isInputRange!(Unqual!R)) { - static if (is(R : InputRange!(ElementType!R))) { - alias InputRangeObject = R; - } else static if (!is(Unqual!R == R)) { - alias InputRangeObject = InputRangeObject!(Unqual!R); - } else { + static if (isInfinite!Range) + enum bool empty = false; + else + { + bool empty() @property + { + return range.empty; + } + } - /// - class InputRangeObject : MostDerivedInputRange!(R) { - private R _range; - private alias E = ElementType!R; + void popFront() + { + assert(!range.empty); + range.popFront(); + ++index; // When !hasLength!Range, overflow is expected + } - this(R range) { - this._range = range; + static if (isForwardRange!Range) + { + Result save() @property + { + return typeof(return)(range.save, index); } + } - @property E front() { return _range.front; } - - E moveFront() { - return .moveFront(_range); + static if (hasLength!Range) + { + size_t length() @property + { + return range.length; } - void popFront() { _range.popFront(); } - @property bool empty() { return _range.empty; } + alias opDollar = length; - static if (isForwardRange!R) { - @property typeof(this) save() { - return new typeof(this)(_range.save); + static if (isBidirectionalRange!Range) + { + ElemType back() @property + { + assert(!range.empty); + return typeof(return)(cast(Enumerator)(index + range.length - 1), range.back); } - } - static if (hasAssignableElements!R) { - @property void front(E newVal) { - _range.front = newVal; + void popBack() + { + assert(!range.empty); + range.popBack(); } } + } - static if (isBidirectionalRange!R) { - @property E back() { return _range.back; } + static if (isRandomAccessRange!Range) + { + ElemType opIndex(size_t i) + { + return typeof(return)(cast(Enumerator)(index + i), range[i]); + } + } - E moveBack() { - return .moveBack(_range); + static if (hasSlicing!Range) + { + static if (hasLength!Range) + { + Result opSlice(size_t i, size_t j) + { + return typeof(return)(range[i .. j], cast(Enumerator)(index + i)); } + } + else + { + static struct DollarToken {} + enum opDollar = DollarToken.init; - void popBack() { return _range.popBack(); } + Result opSlice(size_t i, DollarToken) + { + return typeof(return)(range[i .. $], cast(Enumerator)(index + i)); + } - static if (hasAssignableElements!R) { - @property void back(E newVal) { - _range.back = newVal; - } + auto opSlice(size_t i, size_t j) + { + return this[i .. $].takeExactly(j - 1); } } + } + } - static if (isRandomAccessRange!R) { - E opIndex(size_t index) { - return _range[index]; - } + return Result(range, start); +} - E moveAt(size_t index) { - return .moveAt(_range, index); - } +/// Can start enumeration from a negative position: +pure @safe nothrow unittest +{ + import std.array : assocArray; + import std.range : enumerate; - static if (hasAssignableElements!R) { - void opIndexAssign(E val, size_t index) { - _range[index] = val; - } - } + bool[int] aa = true.repeat(3).enumerate(-1).assocArray(); + assert(aa[-1]); + assert(aa[0]); + assert(aa[1]); +} - static if (!isInfinite!R) { - @property size_t length() { - return _range.length; - } +pure @safe nothrow unittest +{ + import std.internal.test.dummyrange; - alias opDollar = length; + import std.typecons : tuple; - // Can't support slicing until all the issues with - // requiring slicing support for finite random access - // ranges are resolved. - version(none) { - typeof(this) opSlice(size_t lower, size_t upper) { - return new typeof(this)(_range[lower..upper]); - } - } - } - } + static struct HasSlicing + { + typeof(this) front() @property { return typeof(this).init; } + bool empty() @property { return true; } + void popFront() {} - // Optimization: One delegate call is faster than three virtual - // function calls. Use opApply for foreach syntax. - int opApply(int delegate(E) dg) { - int res; + typeof(this) opSlice(size_t, size_t) + { + return typeof(this)(); + } + } - for(auto r = _range; !r.empty; r.popFront()) { - res = dg(r.front); - if (res) break; - } + foreach (DummyType; TypeTuple!(AllDummyRanges, HasSlicing)) + { + alias R = typeof(enumerate(DummyType.init)); + static assert(isInputRange!R); + static assert(isForwardRange!R == isForwardRange!DummyType); + static assert(isRandomAccessRange!R == isRandomAccessRange!DummyType); + static assert(!hasAssignableElements!R); - return res; - } + static if (hasLength!DummyType) + { + static assert(hasLength!R); + static assert(isBidirectionalRange!R == + isBidirectionalRange!DummyType); + } - int opApply(int delegate(size_t, E) dg) { - int res; + static assert(hasSlicing!R == hasSlicing!DummyType); + } - size_t i = 0; - for(auto r = _range; !r.empty; r.popFront()) { - res = dg(i, r.front); - if (res) break; - i++; - } + static immutable values = ["zero", "one", "two", "three"]; + auto enumerated = values[].enumerate(); + assert(!enumerated.empty); + assert(enumerated.front == tuple(0, "zero")); + assert(enumerated.back == tuple(3, "three")); - return res; - } - } + typeof(enumerated) saved = enumerated.save; + saved.popFront(); + assert(enumerated.front == tuple(0, "zero")); + assert(saved.front == tuple(1, "one")); + assert(saved.length == enumerated.length - 1); + saved.popBack(); + assert(enumerated.back == tuple(3, "three")); + assert(saved.back == tuple(2, "two")); + saved.popFront(); + assert(saved.front == tuple(2, "two")); + assert(saved.back == tuple(2, "two")); + saved.popFront(); + assert(saved.empty); + + size_t control = 0; + foreach (i, v; enumerated) + { + static assert(is(typeof(i) == size_t)); + static assert(is(typeof(v) == typeof(values[0]))); + assert(i == control); + assert(v == values[i]); + assert(tuple(i, v) == enumerated[i]); + ++control; } -} -/**Convenience function for creating an $(D InputRangeObject) of the proper type. - * See $(LREF InputRange) for an example. - */ -InputRangeObject!R inputRangeObject(R)(R range) if (isInputRange!R) { - static if (is(R : InputRange!(ElementType!R))) { - return range; - } else { - return new InputRangeObject!R(range); + assert(enumerated[0 .. $].front == tuple(0, "zero")); + assert(enumerated[$ - 1 .. $].front == tuple(3, "three")); + + foreach(i; 0 .. 10) + { + auto shifted = values[0 .. 2].enumerate(i); + assert(shifted.front == tuple(i, "zero")); + assert(shifted[0] == shifted.front); + + auto next = tuple(i + 1, "one"); + assert(shifted[1] == next); + shifted.popFront(); + assert(shifted.front == next); + shifted.popFront(); + assert(shifted.empty); } -} -/**Convenience function for creating an $(D OutputRangeObject) with a base range - * of type $(D R) that accepts types $(D E). + foreach(T; TypeTuple!(ubyte, byte, uint, int)) + { + auto inf = 42.repeat().enumerate(T.max); + alias Inf = typeof(inf); + static assert(isInfinite!Inf); + static assert(hasSlicing!Inf); - Examples: - --- - uint[] outputArray; - auto app = appender(&outputArray); - auto appWrapped = outputRangeObject!(uint, uint[])(app); - static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); - static assert(is(typeof(appWrapped) : OutputRange!(uint))); - --- -*/ -template outputRangeObject(E...) { + // test overflow + assert(inf.front == tuple(T.max, 42)); + inf.popFront(); + assert(inf.front == tuple(T.min, 42)); - /// - OutputRangeObject!(R, E) outputRangeObject(R)(R range) { - return new OutputRangeObject!(R, E)(range); + // test slicing + inf = inf[42 .. $]; + assert(inf.front == tuple(T.min + 42, 42)); + auto window = inf[0 .. 2]; + assert(window.length == 1); + assert(window.front == inf.front); + window.popFront(); + assert(window.empty); } } -unittest { - static void testEquality(R)(iInputRange r1, R r2) { - assert(equal(r1, r2)); +pure @safe unittest +{ + import std.algorithm : equal; + static immutable int[] values = [0, 1, 2, 3, 4]; + foreach(T; TypeTuple!(ubyte, ushort, uint, ulong)) + { + auto enumerated = values.enumerate!T(); + static assert(is(typeof(enumerated.front.index) == T)); + assert(enumerated.equal(values[].zip(values))); + + foreach(T i; 0 .. 5) + { + auto subset = values[cast(size_t)i .. $]; + auto offsetEnumerated = subset.enumerate(i); + static assert(is(typeof(enumerated.front.index) == T)); + assert(offsetEnumerated.equal(subset.zip(subset))); + } } +} - auto arr = [1,2,3,4]; - RandomFiniteAssignable!int arrWrapped = inputRangeObject(arr); - static assert(isRandomAccessRange!(typeof(arrWrapped))); - // static assert(hasSlicing!(typeof(arrWrapped))); - static assert(hasLength!(typeof(arrWrapped))); - arrWrapped[0] = 0; - assert(arr[0] == 0); - assert(arr.moveFront() == 0); - assert(arr.moveBack() == 4); - assert(arr.moveAt(1) == 2); +version(none) // @@@BUG@@@ 10939 +{ + // Re-enable (or remove) if 10939 is resolved. + /+pure+/ unittest // Impure because of std.conv.to + { + import core.exception : RangeError; + import std.exception : assertNotThrown, assertThrown; - foreach(elem; arrWrapped) {} - foreach(i, elem; arrWrapped) {} + static immutable values = [42]; - assert(inputRangeObject(arrWrapped) is arrWrapped); + static struct SignedLengthRange + { + immutable(int)[] _values = values; - foreach(DummyType; AllDummyRanges) { - auto d = DummyType.init; - static assert(propagatesRangeType!(DummyType, - typeof(inputRangeObject(d)))); - static assert(propagatesRangeType!(DummyType, - MostDerivedInputRange!DummyType)); - InputRange!uint wrapped = inputRangeObject(d); - assert(equal(wrapped, d)); - } + int front() @property { assert(false); } + bool empty() @property { assert(false); } + void popFront() { assert(false); } + + int length() @property + { + return cast(int)_values.length; + } + } + + SignedLengthRange svalues; + foreach(Enumerator; TypeTuple!(ubyte, byte, ushort, short, uint, int, ulong, long)) + { + assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max)); + assertNotThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length)); + assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length + 1)); + + assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max)); + assertNotThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length)); + assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length + 1)); + } - // Test output range stuff. - auto app = appender!(uint[])(); - auto appWrapped = outputRangeObject!(uint, uint[])(app); - static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); - static assert(is(typeof(appWrapped) : OutputRange!(uint))); + foreach(Enumerator; TypeTuple!(byte, short, int)) + { + assertThrown!RangeError(repeat(0, uint.max).enumerate!Enumerator()); + } - appWrapped.put(1); - appWrapped.put([2, 3]); - assert(app.data.length == 3); - assert(equal(app.data, [1,2,3])); + assertNotThrown!RangeError(repeat(0, uint.max).enumerate!long()); + } } /** @@ -8513,6 +6722,7 @@ if (isInputRange!Range) static if (isRandomAccessRange!Range) { + import std.algorithm : isSorted; // Check the sortedness of the input if (this._input.length < 2) return; immutable size_t msb = bsr(this._input.length) + 1; @@ -8608,6 +6818,7 @@ if (isInputRange!Range) */ auto release() { + import std.algorithm : move; return move(_input); } @@ -8924,6 +7135,7 @@ sgi.com/tech/stl/binary_search.html, binary_search). /// unittest { + import std.algorithm : sort; auto a = [ 1, 2, 3, 42, 52, 64 ]; auto r = assumeSorted(a); assert(r.contains(3)); @@ -8943,8 +7155,9 @@ No copy of the original range is ever made. If the underlying range is changed concurrently with its corresponding $(D SortedRange) in ways that break its sortedness, $(D SortedRange) will work erratically. */ -unittest +@safe unittest { + import std.algorithm : swap; auto a = [ 1, 2, 3, 42, 52, 64 ]; auto r = assumeSorted(a); assert(r.contains(42)); @@ -8952,8 +7165,10 @@ unittest assert(!r.contains(42)); // passes although it shouldn't } -unittest +@safe unittest { + import std.algorithm : equal; + auto a = [ 10, 20, 30, 30, 30, 40, 40, 50, 60 ]; auto r = assumeSorted(a).trisect(30); assert(equal(r[0], [ 10, 20 ])); @@ -8966,8 +7181,9 @@ unittest assert(equal(r[2], [ 40, 40, 50, 60 ])); } -unittest +@safe unittest { + import std.algorithm : equal; auto a = [ "A", "AG", "B", "E", "F" ]; auto r = assumeSorted!"cmp(a,b) < 0"(a).trisect("B"w); assert(equal(r[0], [ "A", "AG" ])); @@ -8979,8 +7195,9 @@ unittest assert(equal(r[2], [ "AG", "B", "E", "F" ])); } -unittest +@safe unittest { + import std.algorithm : equal; static void test(SearchPolicy pol)() { auto a = [ 1, 2, 3, 42, 52, 64 ]; @@ -9013,7 +7230,7 @@ unittest test!(SearchPolicy.binarySearch)(); } -unittest +@safe unittest { // Check for small arrays int[] a; @@ -9026,8 +7243,9 @@ unittest r = assumeSorted(a); } -unittest +@safe unittest { + import std.algorithm : swap; auto a = [ 1, 2, 3, 42, 52, 64 ]; auto r = assumeSorted(a); assert(r.contains(42)); @@ -9035,7 +7253,7 @@ unittest assert(!r.contains(42)); // passes although it shouldn't } -unittest +@safe unittest { immutable(int)[] arr = [ 1, 2, 3 ]; auto s = assumeSorted(arr); @@ -9078,8 +7296,9 @@ if (isInputRange!(Unqual!R)) return SortedRange!(Unqual!R, pred)(r); } -unittest +@safe unittest { + import std.algorithm : equal; static assert(isRandomAccessRange!(SortedRange!(int[]))); int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; auto p = assumeSorted(a).lowerBound(4); @@ -9092,8 +7311,9 @@ unittest assert(equal(p, [ 0, 1, 2, 3, 4, 5, 6])); } -unittest +@safe unittest { + import std.algorithm : equal; int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; auto p = assumeSorted(a).upperBound(3); assert(equal(p, [4, 4, 5, 6 ])); @@ -9101,9 +7321,10 @@ unittest assert(equal(p, [ 5, 6 ])); } -unittest +@safe unittest { import std.conv : text; + import std.algorithm : equal; int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; auto p = assumeSorted(a).equalRange(3); @@ -9120,7 +7341,7 @@ unittest assert(equal(p, [ 3, 3, 3])); } -unittest +@safe unittest { int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; if (a.length) @@ -9131,7 +7352,7 @@ unittest } } -unittest +@safe unittest { auto a = [ 5, 7, 34, 345, 677 ]; auto r = assumeSorted(a); @@ -9139,6 +7360,10 @@ unittest r = assumeSorted(a); a = [ 1 ]; r = assumeSorted(a); +} + +unittest +{ bool ok = true; try { @@ -9164,31 +7389,6 @@ unittest $(D save) is ever called on the RefRange, then no operations on the saved range will affect the original. - Examples: --------------------- -import std.algorithm; -ubyte[] buffer = [1, 9, 45, 12, 22]; -auto found1 = find(buffer, 45); -assert(found1 == [45, 12, 22]); -assert(buffer == [1, 9, 45, 12, 22]); - -auto wrapped1 = refRange(&buffer); -auto found2 = find(wrapped1, 45); -assert(*found2.ptr == [45, 12, 22]); -assert(buffer == [45, 12, 22]); - -auto found3 = find(wrapped2.save, 22); -assert(*found3.ptr == [22]); -assert(buffer == [45, 12, 22]); - -string str = "hello world"; -auto wrappedStr = refRange(&str); -assert(str.front == 'h'); -str.popFrontN(5); -assert(str == " world"); -assert(wrappedStr.front == ' '); -assert(*wrappedStr.ptr == " world"); --------------------- +/ struct RefRange(R) if(isForwardRange!R) @@ -9210,51 +7410,6 @@ public: one exception is when a $(D RefRange) is assigned $(D null) either directly or because $(D rhs) is $(D null). In that case, $(D RefRange) no longer refers to the original range but is $(D null). - - Examples: --------------------- -ubyte[] buffer1 = [1, 2, 3, 4, 5]; -ubyte[] buffer2 = [6, 7, 8, 9, 10]; -auto wrapped1 = refRange(&buffer1); -auto wrapped2 = refRange(&buffer2); -assert(wrapped1.ptr is &buffer1); -assert(wrapped2.ptr is &buffer2); -assert(wrapped1.ptr !is wrapped2.ptr); -assert(buffer1 != buffer2); - -wrapped1 = wrapped2; - -//Everything points to the same stuff as before. -assert(wrapped1.ptr is &buffer1); -assert(wrapped2.ptr is &buffer2); -assert(wrapped1.ptr !is wrapped2.ptr); - -//But buffer1 has changed due to the assignment. -assert(buffer1 == [6, 7, 8, 9, 10]); -assert(buffer2 == [6, 7, 8, 9, 10]); - -buffer2 = [11, 12, 13, 14, 15]; - -//Everything points to the same stuff as before. -assert(wrapped1.ptr is &buffer1); -assert(wrapped2.ptr is &buffer2); -assert(wrapped1.ptr !is wrapped2.ptr); - -//But buffer2 has changed due to the assignment. -assert(buffer1 == [6, 7, 8, 9, 10]); -assert(buffer2 == [11, 12, 13, 14, 15]); - -wrapped2 = null; - -//The pointer changed for wrapped2 but not wrapped1. -assert(wrapped1.ptr is &buffer1); -assert(wrapped2.ptr is null); -assert(wrapped1.ptr !is wrapped2.ptr); - -//buffer2 is not affected by the assignment. -assert(buffer1 == [6, 7, 8, 9, 10]); -assert(buffer2 == [11, 12, 13, 14, 15]); --------------------- +/ auto opAssign(RefRange rhs) { @@ -9563,7 +7718,7 @@ private: R* _range; } -//Verify Example. +/// Basic Example unittest { import std.algorithm; @@ -9590,7 +7745,7 @@ unittest assert(*wrappedStr.ptr == " world"); } -//Verify opAssign Example. +/// opAssign Example. unittest { ubyte[] buffer1 = [1, 2, 3, 4, 5]; @@ -9837,7 +7992,7 @@ unittest assert(wrapper[2] == 2); assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); - assert(*wrapper[3 .. 6].ptr, [41, 3, 40]); + assert(*wrapper[3 .. 6].ptr != null, [41, 3, 40]); assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); } @@ -9927,8 +8082,9 @@ auto refRange(R)(R* range) /*****************************************************************************/ -unittest // bug 9060 +@safe unittest // bug 9060 { + import std.algorithm : map, joiner, group, until; // fix for std.algorithm auto r = map!(x => 0)([1]); chain(r, r); @@ -9976,9 +8132,9 @@ struct NullSink void put(E)(E){} } -unittest +@safe unittest { - import std.algorithm; + import std.algorithm : map, copy; [4, 5, 6].map!(x => x * 2).copy(NullSink()); } @@ -9996,10 +8152,12 @@ unittest will not actually be executed until the range is "walked" using functions that evaluate ranges, such as $(XREF array,array) or $(XREF algorithm,reduce). + + See_Also: $(XREF argorithm,each) +/ auto tee(Flag!"pipeOnPop" pipeOnPop = Yes.pipeOnPop, R1, R2)(R1 inputRange, R2 outputRange) -if (isInputRange!R1 && isOutputRange!(R2, typeof(inputRange.front))) +if (isInputRange!R1 && isOutputRange!(R2, ElementType!R1)) { static struct Result { @@ -10069,21 +8227,28 @@ if (is(typeof(fun) == void) || isSomeFunction!fun) when using either as an $(LREF OutputRange). Since a template has no type, typeof(template) will always return void. If it's a template lambda, it's first necessary to instantiate - it with the type of $(D inputRange.front). + it with $(D ElementType!R1). */ static if (is(typeof(fun) == void)) + alias _fun = fun!(ElementType!R1); + else + alias _fun = fun; + + static if (isFunctionPointer!_fun || isDelegate!_fun) { - return tee!pipeOnPop(inputRange, fun!(typeof(inputRange.front))); + return tee!pipeOnPop(inputRange, _fun); } else { - return tee!pipeOnPop(inputRange, fun); + return tee!pipeOnPop(inputRange, &_fun); } } // -unittest +@safe unittest { + import std.algorithm : equal, filter, map; + // Pass-through int[] values = [1, 4, 9, 16, 25]; @@ -10103,8 +8268,10 @@ unittest } // -unittest +@safe unittest { + import std.algorithm : equal, filter, map; + int[] values = [1, 4, 9, 16, 25]; int count = 0; @@ -10138,8 +8305,10 @@ unittest } // -unittest +@safe unittest { + import std.algorithm : filter, equal, map; + char[] txt = "Line one, Line 2".dup; bool isVowel(dchar c) @@ -10158,15 +8327,15 @@ unittest assert(shiftedCount == 3); } -unittest +@safe unittest { // Manually stride to test different pipe behavior. void testRange(Range)(Range r) { const int strideLen = 3; int i = 0; - typeof(Range.front) elem1; - typeof(Range.front) elem2; + ElementType!Range elem1; + ElementType!Range elem2; while (!r.empty) { if (i % strideLen == 0) @@ -10195,8 +8364,10 @@ unittest assert(frontCount == 9); } -unittest +@safe unittest { + import std.algorithm : equal; + //Test diverting elements to an OutputRange string txt = "abcdefghijklmnopqrstuvwxyz"; @@ -10232,3 +8403,12 @@ unittest assert(equal(txt, appResult) && equal(appResult, appSink.data)); } } + +@safe unittest +{ + // Issue 13483 + static void func1(T)(T x) {} + void func2(int x) {} + + auto r = [1, 2, 3, 4].tee!func1.tee!func2; +} diff --git a/std/range/primitives.d b/std/range/primitives.d new file mode 100644 index 00000000000..b7b293d4099 --- /dev/null +++ b/std/range/primitives.d @@ -0,0 +1,2232 @@ +/** +This module is a submodule of $(LINK2 std_range_package.html, std.range). + +It provides basic range functionality by defining several templates for testing +whether a given object is a _range, and what kind of _range it is: + +$(BOOKTABLE , + $(TR $(TD $(D $(LREF isInputRange))) + $(TD Tests if something is an $(I input _range), defined to be + something from which one can sequentially read data using the + primitives $(D front), $(D popFront), and $(D empty). + )) + $(TR $(TD $(D $(LREF isOutputRange))) + $(TD Tests if something is an $(I output _range), defined to be + something to which one can sequentially write data using the + $(D $(LREF put)) primitive. + )) + $(TR $(TD $(D $(LREF isForwardRange))) + $(TD Tests if something is a $(I forward _range), defined to be an + input _range with the additional capability that one can save one's + current position with the $(D save) primitive, thus allowing one to + iterate over the same _range multiple times. + )) + $(TR $(TD $(D $(LREF isBidirectionalRange))) + $(TD Tests if something is a $(I bidirectional _range), that is, a + forward _range that allows reverse traversal using the primitives $(D + back) and $(D popBack). + )) + $(TR $(TD $(D $(LREF isRandomAccessRange))) + $(TD Tests if something is a $(I random access _range), which is a + bidirectional _range that also supports the array subscripting + operation via the primitive $(D opIndex). + )) +) + +It also provides number of templates that test for various _range capabilities: + +$(BOOKTABLE , + $(TR $(TD $(D $(LREF hasMobileElements))) + $(TD Tests if a given _range's elements can be moved around using the + primitives $(D moveFront), $(D moveBack), or $(D moveAt). + )) + $(TR $(TD $(D $(LREF ElementType))) + $(TD Returns the element type of a given _range. + )) + $(TR $(TD $(D $(LREF ElementEncodingType))) + $(TD Returns the encoding element type of a given _range. + )) + $(TR $(TD $(D $(LREF hasSwappableElements))) + $(TD Tests if a _range is a forward _range with swappable elements. + )) + $(TR $(TD $(D $(LREF hasAssignableElements))) + $(TD Tests if a _range is a forward _range with mutable elements. + )) + $(TR $(TD $(D $(LREF hasLvalueElements))) + $(TD Tests if a _range is a forward _range with elements that can be + passed by reference and have their address taken. + )) + $(TR $(TD $(D $(LREF hasLength))) + $(TD Tests if a given _range has the $(D length) attribute. + )) + $(TR $(TD $(D $(LREF isInfinite))) + $(TD Tests if a given _range is an $(I infinite _range). + )) + $(TR $(TD $(D $(LREF hasSlicing))) + $(TD Tests if a given _range supports the array slicing operation $(D + R[x..y]). + )) +) + +Finally, it includes some convenience functions for manipulating ranges: + +$(BOOKTABLE , + $(TR $(TD $(D $(LREF popFrontN))) + $(TD Advances a given _range by up to $(I n) elements. + )) + $(TR $(TD $(D $(LREF popBackN))) + $(TD Advances a given bidirectional _range from the right by up to + $(I n) elements. + )) + $(TR $(TD $(D $(LREF popFrontExactly))) + $(TD Advances a given _range by up exactly $(I n) elements. + )) + $(TR $(TD $(D $(LREF popBackExactly))) + $(TD Advances a given bidirectional _range from the right by exactly + $(I n) elements. + )) + $(TR $(TD $(D $(LREF moveFront))) + $(TD Removes the front element of a _range. + )) + $(TR $(TD $(D $(LREF moveBack))) + $(TD Removes the back element of a bidirectional _range. + )) + $(TR $(TD $(D $(LREF moveAt))) + $(TD Removes the $(I i)'th element of a random-access _range. + )) + $(TR $(TD $(D $(LREF walkLength))) + $(TD Computes the length of any _range in O(n) time. + )) +) + +Source: $(PHOBOSSRC std/range/_constraints.d) + +Macros: + +WIKI = Phobos/StdRange + +Copyright: Copyright by authors 2008-. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha, +and Jonathan M Davis. Credit for some of the ideas in building this module goes +to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi). +*/ +module std.range.primitives; + +import std.traits; + +/** +Returns $(D true) if $(D R) is an input range. An input range must +define the primitives $(D empty), $(D popFront), and $(D front). The +following code should compile for any input range. + +---- +R r; // can define a range object +if (r.empty) {} // can test for empty +r.popFront(); // can invoke popFront() +auto h = r.front; // can get the front of the range of non-void type +---- + +The semantics of an input range (not checkable during compilation) are +assumed to be the following ($(D r) is an object of type $(D R)): + +$(UL $(LI $(D r.empty) returns $(D false) iff there is more data +available in the range.) $(LI $(D r.front) returns the current +element in the range. It may return by value or by reference. Calling +$(D r.front) is allowed only if calling $(D r.empty) has, or would +have, returned $(D false).) $(LI $(D r.popFront) advances to the next +element in the range. Calling $(D r.popFront) is allowed only if +calling $(D r.empty) has, or would have, returned $(D false).)) + */ +template isInputRange(R) +{ + enum bool isInputRange = is(typeof( + (inout int = 0) + { + R r = R.init; // can define a range object + if (r.empty) {} // can test for empty + r.popFront(); // can invoke popFront() + auto h = r.front; // can get the front of the range + })); +} + +@safe unittest +{ + struct A {} + struct B + { + void popFront(); + @property bool empty(); + @property int front(); + } + static assert(!isInputRange!(A)); + static assert( isInputRange!(B)); + static assert( isInputRange!(int[])); + static assert( isInputRange!(char[])); + static assert(!isInputRange!(char[4])); + static assert( isInputRange!(inout(int)[])); // bug 7824 +} + +/+ +puts the whole raw element $(D e) into $(D r). doPut will not attempt to +iterate, slice or transcode $(D e) in any way shape or form. It will $(B only) +call the correct primitive ($(D r.put(e)), $(D r.front = e) or +$(D r(0)) once. + +This can be important when $(D e) needs to be placed in $(D r) unchanged. +Furthermore, it can be useful when working with $(D InputRange)s, as doPut +guarantees that no more than a single element will be placed. ++/ +private void doPut(R, E)(ref R r, auto ref E e) +{ + static if(is(PointerTarget!R == struct)) + enum usingPut = hasMember!(PointerTarget!R, "put"); + else + enum usingPut = hasMember!(R, "put"); + + static if (usingPut) + { + static assert(is(typeof(r.put(e))), + "Cannot nativaly put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + r.put(e); + } + else static if (isInputRange!R) + { + static assert(is(typeof(r.front = e)), + "Cannot nativaly put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + r.front = e; + r.popFront(); + } + else static if (is(typeof(r(e)))) + { + r(e); + } + else + { + static assert (false, + "Cannot nativaly put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + } +} + +@safe unittest +{ + static assert (!isNativeOutputRange!(int, int)); + static assert ( isNativeOutputRange!(int[], int)); + static assert (!isNativeOutputRange!(int[][], int)); + + static assert (!isNativeOutputRange!(int, int[])); + static assert (!isNativeOutputRange!(int[], int[])); + static assert ( isNativeOutputRange!(int[][], int[])); + + static assert (!isNativeOutputRange!(int, int[][])); + static assert (!isNativeOutputRange!(int[], int[][])); + static assert (!isNativeOutputRange!(int[][], int[][])); + + static assert (!isNativeOutputRange!(int[4], int)); + static assert ( isNativeOutputRange!(int[4][], int)); //Scary! + static assert ( isNativeOutputRange!(int[4][], int[4])); + + static assert (!isNativeOutputRange!( char[], char)); + static assert (!isNativeOutputRange!( char[], dchar)); + static assert ( isNativeOutputRange!(dchar[], char)); + static assert ( isNativeOutputRange!(dchar[], dchar)); + +} + +/++ +Outputs $(D e) to $(D r). The exact effect is dependent upon the two +types. Several cases are accepted, as described below. The code snippets +are attempted in order, and the first to compile "wins" and gets +evaluated. + +In this table "doPut" is a method that places $(D e) into $(D r), using the +correct primitive: $(D r.put(e)) if $(D R) defines $(D put), $(D r.front = e) +if $(D r) is an input range (followed by $(D r.popFront())), or $(D r(e)) +otherwise. + +$(BOOKTABLE , + $(TR + $(TH Code Snippet) + $(TH Scenario) + ) + $(TR + $(TD $(D r.doPut(e);)) + $(TD $(D R) specifically accepts an $(D E).) + ) + $(TR + $(TD $(D r.doPut([ e ]);)) + $(TD $(D R) specifically accepts an $(D E[]).) + ) + $(TR + $(TD $(D r.putChar(e);)) + $(TD $(D R) accepts some form of string or character. put will + transcode the character $(D e) accordingly.) + ) + $(TR + $(TD $(D for (; !e.empty; e.popFront()) put(r, e.front);)) + $(TD Copying range $(D E) into $(D R).) + ) +) + +Tip: $(D put) should $(I not) be used "UFCS-style", e.g. $(D r.put(e)). +Doing this may call $(D R.put) directly, by-passing any transformation +feature provided by $(D Range.put). $(D put(r, e)) is prefered. + +/ +void put(R, E)(ref R r, E e) +{ + //First level: simply straight up put. + static if (is(typeof(doPut(r, e)))) + { + doPut(r, e); + } + //Optional optimization block for straight up array to array copy. + else static if (isDynamicArray!R && !isNarrowString!R && isDynamicArray!E && is(typeof(r[] = e[]))) + { + immutable len = e.length; + r[0 .. len] = e[]; + r = r[len .. $]; + } + //Accepts E[] ? + else static if (is(typeof(doPut(r, [e]))) && !isDynamicArray!R) + { + if (__ctfe) + { + E[1] arr = [e]; + doPut(r, arr[]); + } + else + doPut(r, (ref e) @trusted { return (&e)[0..1]; }(e)); + } + //special case for char to string. + else static if (isSomeChar!E && is(typeof(putChar(r, e)))) + { + putChar(r, e); + } + //Extract each element from the range + //We can use "put" here, so we can recursively test a RoR of E. + else static if (isInputRange!E && is(typeof(put(r, e.front)))) + { + //Special optimization: If E is a narrow string, and r accepts characters no-wider than the string's + //Then simply feed the characters 1 by 1. + static if (isNarrowString!E && ( + (is(E : const char[]) && is(typeof(doPut(r, char.max))) && !is(typeof(doPut(r, dchar.max))) && !is(typeof(doPut(r, wchar.max)))) || + (is(E : const wchar[]) && is(typeof(doPut(r, wchar.max))) && !is(typeof(doPut(r, dchar.max)))) ) ) + { + foreach(c; e) + doPut(r, c); + } + else + { + for (; !e.empty; e.popFront()) + put(r, e.front); + } + } + else + { + static assert (false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + } +} + +@safe pure nothrow @nogc unittest +{ + static struct R() { void put(in char[]) {} } + R!() r; + put(r, 'a'); +} + +//Helper function to handle chars as quickly and as elegantly as possible +//Assumes r.put(e)/r(e) has already been tested +private void putChar(R, E)(ref R r, E e) +if (isSomeChar!E) +{ + ////@@@9186@@@: Can't use (E[]).init + ref const( char)[] cstringInit(); + ref const(wchar)[] wstringInit(); + ref const(dchar)[] dstringInit(); + + enum csCond = !isDynamicArray!R && is(typeof(doPut(r, cstringInit()))); + enum wsCond = !isDynamicArray!R && is(typeof(doPut(r, wstringInit()))); + enum dsCond = !isDynamicArray!R && is(typeof(doPut(r, dstringInit()))); + + //Use "max" to avoid static type demotion + enum ccCond = is(typeof(doPut(r, char.max))); + enum wcCond = is(typeof(doPut(r, wchar.max))); + //enum dcCond = is(typeof(doPut(r, dchar.max))); + + //Fast transform a narrow char into a wider string + static if ((wsCond && E.sizeof < wchar.sizeof) || (dsCond && E.sizeof < dchar.sizeof)) + { + enum w = wsCond && E.sizeof < wchar.sizeof; + Select!(w, wchar, dchar) c = e; + typeof(c)[1] arr = [c]; + doPut(r, arr[]); + } + //Encode a wide char into a narrower string + else static if (wsCond || csCond) + { + import std.utf : encode; + /+static+/ Select!(wsCond, wchar[2], char[4]) buf; //static prevents purity. + doPut(r, buf[0 .. encode(buf, e)]); + } + //Slowly encode a wide char into a series of narrower chars + else static if (wcCond || ccCond) + { + import std.encoding : encode; + alias C = Select!(wcCond, wchar, char); + encode!(C, R)(e, r); + } + else + { + static assert (false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + } +} + +pure unittest +{ + auto f = delegate (const(char)[]) {}; + putChar(f, cast(dchar)'a'); +} + + +@safe pure unittest +{ + static struct R() { void put(in char[]) {} } + R!() r; + putChar(r, 'a'); +} + +unittest +{ + struct A {} + static assert(!isInputRange!(A)); + struct B + { + void put(int) {} + } + B b; + put(b, 5); +} + +unittest +{ + int[] a = [1, 2, 3], b = [10, 20]; + auto c = a; + put(a, b); + assert(c == [10, 20, 3]); + assert(a == [3]); +} + +unittest +{ + int[] a = new int[10]; + int b; + static assert(isInputRange!(typeof(a))); + put(a, b); +} + +unittest +{ + void myprint(in char[] s) { } + auto r = &myprint; + put(r, 'a'); +} + +unittest +{ + int[] a = new int[10]; + static assert(!__traits(compiles, put(a, 1.0L))); + static assert( __traits(compiles, put(a, 1))); + /* + * a[0] = 65; // OK + * a[0] = 'A'; // OK + * a[0] = "ABC"[0]; // OK + * put(a, "ABC"); // OK + */ + static assert( __traits(compiles, put(a, "ABC"))); +} + +unittest +{ + char[] a = new char[10]; + static assert(!__traits(compiles, put(a, 1.0L))); + static assert(!__traits(compiles, put(a, 1))); + // char[] is NOT output range. + static assert(!__traits(compiles, put(a, 'a'))); + static assert(!__traits(compiles, put(a, "ABC"))); +} + +unittest +{ + int[][] a; + int[] b; + int c; + static assert( __traits(compiles, put(b, c))); + static assert( __traits(compiles, put(a, b))); + static assert(!__traits(compiles, put(a, c))); +} + +unittest +{ + int[][] a = new int[][](3); + int[] b = [1]; + auto aa = a; + put(aa, b); + assert(aa == [[], []]); + assert(a == [[1], [], []]); + int[][3] c = [2]; + aa = a; + put(aa, c[]); + assert(aa.empty); + assert(a == [[2], [2], [2]]); +} + +unittest +{ + // Test fix for bug 7476. + struct LockingTextWriter + { + void put(dchar c){} + } + struct RetroResult + { + bool end = false; + @property bool empty() const { return end; } + @property dchar front(){ return 'a'; } + void popFront(){ end = true; } + } + LockingTextWriter w; + RetroResult r; + put(w, r); +} + +unittest +{ + import std.conv : to; + import std.typecons : tuple; + import std.typetuple; + + static struct PutC(C) + { + string result; + void put(const(C) c) { result ~= to!string((&c)[0..1]); } + } + static struct PutS(C) + { + string result; + void put(const(C)[] s) { result ~= to!string(s); } + } + static struct PutSS(C) + { + string result; + void put(const(C)[][] ss) + { + foreach(s; ss) + result ~= to!string(s); + } + } + + PutS!char p; + putChar(p, cast(dchar)'a'); + + //Source Char + foreach (SC; TypeTuple!(char, wchar, dchar)) + { + SC ch = 'I'; + dchar dh = '♥'; + immutable(SC)[] s = "日本語!"; + immutable(SC)[][] ss = ["日本語", "が", "好き", "ですか", "?"]; + + //Target Char + foreach (TC; TypeTuple!(char, wchar, dchar)) + { + //Testing PutC and PutS + foreach (Type; TypeTuple!(PutC!TC, PutS!TC)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + Type type; + auto sink = new Type(); + + //Testing put and sink + foreach (value ; tuple(type, sink)) + { + put(value, ch); + assert(value.result == "I"); + put(value, dh); + assert(value.result == "I♥"); + put(value, s); + assert(value.result == "I♥日本語!"); + put(value, ss); + assert(value.result == "I♥日本語!日本語が好きですか?"); + } + }(); + } + } +} + +unittest +{ + static struct CharRange + { + char c; + enum empty = false; + void popFront(){}; + ref char front() return @property + { + return c; + } + } + CharRange c; + put(c, cast(dchar)'H'); + put(c, "hello"d); +} + +unittest +{ + // issue 9823 + const(char)[] r; + void delegate(const(char)[]) dg = (s) { r = s; }; + put(dg, ["ABC"]); + assert(r == "ABC"); +} + +unittest +{ + // issue 10571 + import std.format; + string buf; + formattedWrite((in char[] s) { buf ~= s; }, "%s", "hello"); + assert(buf == "hello"); +} + +unittest +{ + import std.format; + import std.typetuple; + struct PutC(C) + { + void put(C){} + } + struct PutS(C) + { + void put(const(C)[]){} + } + struct CallC(C) + { + void opCall(C){} + } + struct CallS(C) + { + void opCall(const(C)[]){} + } + struct FrontC(C) + { + enum empty = false; + auto front()@property{return C.init;} + void front(C)@property{} + void popFront(){} + } + struct FrontS(C) + { + enum empty = false; + auto front()@property{return C[].init;} + void front(const(C)[])@property{} + void popFront(){} + } + void foo() + { + foreach(C; TypeTuple!(char, wchar, dchar)) + { + formattedWrite((C c){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite((const(C)[]){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(PutC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(PutS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + CallC!C callC; + CallS!C callS; + formattedWrite(callC, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(callS, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(FrontC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(FrontS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + } + formattedWrite((dchar[]).init, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + } +} + +/+ +Returns $(D true) if $(D R) is a native output range for elements of type +$(D E). An output range is defined functionally as a range that +supports the operation $(D doPut(r, e)) as defined above. if $(D doPut(r, e)) +is valid, then $(D put(r,e)) will have the same behavior. + +The two guarantees isNativeOutputRange gives over the larger $(D isOutputRange) +are: +1: $(D e) is $(B exactly) what will be placed (not $(D [e]), for example). +2: if $(D E) is a non $(empty) $(D InputRange), then placing $(D e) is +guaranteed to not overflow the range. + +/ +package template isNativeOutputRange(R, E) +{ + enum bool isNativeOutputRange = is(typeof( + (inout int = 0) + { + R r = void; + E e; + doPut(r, e); + })); +} + +/// +@safe unittest +{ + int[] r = new int[](4); + static assert(isInputRange!(int[])); + static assert( isNativeOutputRange!(int[], int)); + static assert(!isNativeOutputRange!(int[], int[])); + static assert( isOutputRange!(int[], int[])); + + if (!r.empty) + put(r, 1); //guaranteed to succeed + if (!r.empty) + put(r, [1, 2]); //May actually error out. +} +/++ +Returns $(D true) if $(D R) is an output range for elements of type +$(D E). An output range is defined functionally as a range that +supports the operation $(D put(r, e)) as defined above. + +/ +template isOutputRange(R, E) +{ + enum bool isOutputRange = is(typeof( + (inout int = 0) + { + R r = R.init; + E e = E.init; + put(r, e); + })); +} + +/// +@safe unittest +{ + void myprint(in char[] s) { } + static assert(isOutputRange!(typeof(&myprint), char)); + + static assert(!isOutputRange!(char[], char)); + static assert( isOutputRange!(dchar[], wchar)); + static assert( isOutputRange!(dchar[], dchar)); +} + +@safe unittest +{ + import std.array; + import std.stdio : writeln; + + auto app = appender!string(); + string s; + static assert( isOutputRange!(Appender!string, string)); + static assert( isOutputRange!(Appender!string*, string)); + static assert(!isOutputRange!(Appender!string, int)); + static assert(!isOutputRange!(wchar[], wchar)); + static assert( isOutputRange!(dchar[], char)); + static assert( isOutputRange!(dchar[], string)); + static assert( isOutputRange!(dchar[], wstring)); + static assert( isOutputRange!(dchar[], dstring)); + + static assert(!isOutputRange!(const(int)[], int)); + static assert(!isOutputRange!(inout(int)[], int)); +} + + +/** +Returns $(D true) if $(D R) is a forward range. A forward range is an +input range $(D r) that can save "checkpoints" by saving $(D r.save) +to another value of type $(D R). Notable examples of input ranges that +are $(I not) forward ranges are file/socket ranges; copying such a +range will not save the position in the stream, and they most likely +reuse an internal buffer as the entire stream does not sit in +memory. Subsequently, advancing either the original or the copy will +advance the stream, so the copies are not independent. + +The following code should compile for any forward range. + +---- +static assert(isInputRange!R); +R r1; +static assert (is(typeof(r1.save) == R)); +---- + +Saving a range is not duplicating it; in the example above, $(D r1) +and $(D r2) still refer to the same underlying data. They just +navigate that data independently. + +The semantics of a forward range (not checkable during compilation) +are the same as for an input range, with the additional requirement +that backtracking must be possible by saving a copy of the range +object with $(D save) and using it later. + */ +template isForwardRange(R) +{ + enum bool isForwardRange = isInputRange!R && is(typeof( + (inout int = 0) + { + R r1 = R.init; + static assert (is(typeof(r1.save) == R)); + })); +} + +/// +@safe unittest +{ + static assert(!isForwardRange!(int)); + static assert( isForwardRange!(int[])); + static assert( isForwardRange!(inout(int)[])); +} + +/** +Returns $(D true) if $(D R) is a bidirectional range. A bidirectional +range is a forward range that also offers the primitives $(D back) and +$(D popBack). The following code should compile for any bidirectional +range. + +The semantics of a bidirectional range (not checkable during +compilation) are assumed to be the following ($(D r) is an object of +type $(D R)): + +$(UL $(LI $(D r.back) returns (possibly a reference to) the last +element in the range. Calling $(D r.back) is allowed only if calling +$(D r.empty) has, or would have, returned $(D false).)) + */ +template isBidirectionalRange(R) +{ + enum bool isBidirectionalRange = isForwardRange!R && is(typeof( + (inout int = 0) + { + R r = R.init; + r.popBack(); + auto t = r.back; + auto w = r.front; + static assert(is(typeof(t) == typeof(w))); + })); +} + +/// +unittest +{ + alias R = int[]; + R r = [0,1]; + static assert(isForwardRange!R); // is forward range + r.popBack(); // can invoke popBack + auto t = r.back; // can get the back of the range + auto w = r.front; + static assert(is(typeof(t) == typeof(w))); // same type for front and back +} + +@safe unittest +{ + struct A {} + struct B + { + void popFront(); + @property bool empty(); + @property int front(); + } + struct C + { + @property bool empty(); + @property C save(); + void popFront(); + @property int front(); + void popBack(); + @property int back(); + } + static assert(!isBidirectionalRange!(A)); + static assert(!isBidirectionalRange!(B)); + static assert( isBidirectionalRange!(C)); + static assert( isBidirectionalRange!(int[])); + static assert( isBidirectionalRange!(char[])); + static assert( isBidirectionalRange!(inout(int)[])); +} + +/** +Returns $(D true) if $(D R) is a random-access range. A random-access +range is a bidirectional range that also offers the primitive $(D +opIndex), OR an infinite forward range that offers $(D opIndex). In +either case, the range must either offer $(D length) or be +infinite. The following code should compile for any random-access +range. + +The semantics of a random-access range (not checkable during +compilation) are assumed to be the following ($(D r) is an object of +type $(D R)): $(UL $(LI $(D r.opIndex(n)) returns a reference to the +$(D n)th element in the range.)) + +Although $(D char[]) and $(D wchar[]) (as well as their qualified +versions including $(D string) and $(D wstring)) are arrays, $(D +isRandomAccessRange) yields $(D false) for them because they use +variable-length encodings (UTF-8 and UTF-16 respectively). These types +are bidirectional ranges only. + */ +template isRandomAccessRange(R) +{ + enum bool isRandomAccessRange = is(typeof( + (inout int = 0) + { + static assert(isBidirectionalRange!R || + isForwardRange!R && isInfinite!R); + R r = R.init; + auto e = r[1]; + static assert(is(typeof(e) == typeof(r.front))); + static assert(!isNarrowString!R); + static assert(hasLength!R || isInfinite!R); + + static if(is(typeof(r[$]))) + { + static assert(is(typeof(r.front) == typeof(r[$]))); + + static if(!isInfinite!R) + static assert(is(typeof(r.front) == typeof(r[$ - 1]))); + } + })); +} + +/// +unittest +{ + alias R = int[]; + + // range is finite and bidirectional or infinite and forward. + static assert(isBidirectionalRange!R || + isForwardRange!R && isInfinite!R); + + R r = [0,1]; + auto e = r[1]; // can index + static assert(is(typeof(e) == typeof(r.front))); // same type for indexed and front + static assert(!isNarrowString!R); // narrow strings cannot be indexed as ranges + static assert(hasLength!R || isInfinite!R); // must have length or be infinite + + // $ must work as it does with arrays if opIndex works with $ + static if(is(typeof(r[$]))) + { + static assert(is(typeof(r.front) == typeof(r[$]))); + + // $ - 1 doesn't make sense with infinite ranges but needs to work + // with finite ones. + static if(!isInfinite!R) + static assert(is(typeof(r.front) == typeof(r[$ - 1]))); + } +} + +@safe unittest +{ + struct A {} + struct B + { + void popFront(); + @property bool empty(); + @property int front(); + } + struct C + { + void popFront(); + @property bool empty(); + @property int front(); + void popBack(); + @property int back(); + } + struct D + { + @property bool empty(); + @property D save(); + @property int front(); + void popFront(); + @property int back(); + void popBack(); + ref int opIndex(uint); + @property size_t length(); + alias opDollar = length; + //int opSlice(uint, uint); + } + static assert(!isRandomAccessRange!(A)); + static assert(!isRandomAccessRange!(B)); + static assert(!isRandomAccessRange!(C)); + static assert( isRandomAccessRange!(D)); + static assert( isRandomAccessRange!(int[])); + static assert( isRandomAccessRange!(inout(int)[])); +} + +@safe unittest +{ + // Test fix for bug 6935. + struct R + { + @disable this(); + + @property bool empty() const { return false; } + @property int front() const { return 0; } + void popFront() {} + + @property R save() { return this; } + + @property int back() const { return 0; } + void popBack(){} + + int opIndex(size_t n) const { return 0; } + @property size_t length() const { return 0; } + alias opDollar = length; + + void put(int e){ } + } + static assert(isInputRange!R); + static assert(isForwardRange!R); + static assert(isBidirectionalRange!R); + static assert(isRandomAccessRange!R); + static assert(isOutputRange!(R, int)); +} + +/** +Returns $(D true) iff $(D R) is an input range that supports the +$(D moveFront) primitive, as well as $(D moveBack) and $(D moveAt) if it's a +bidirectional or random access range. These may be explicitly implemented, or +may work via the default behavior of the module level functions $(D moveFront) +and friends. The following code should compile for any range +with mobile elements. + +---- +alias E = ElementType!R; +R r; +static assert(isInputRange!R); +static assert(is(typeof(moveFront(r)) == E)); +static if (isBidirectionalRange!R) + static assert(is(typeof(moveBack(r)) == E)); +static if (isRandomAccessRange!R) + static assert(is(typeof(moveAt(r, 0)) == E)); +---- + */ +template hasMobileElements(R) +{ + enum bool hasMobileElements = isInputRange!R && is(typeof( + (inout int = 0) + { + alias E = ElementType!R; + R r = R.init; + static assert(is(typeof(moveFront(r)) == E)); + static if (isBidirectionalRange!R) + static assert(is(typeof(moveBack(r)) == E)); + static if (isRandomAccessRange!R) + static assert(is(typeof(moveAt(r, 0)) == E)); + })); +} + +/// +@safe unittest +{ + import std.algorithm : map; + import std.range : iota, repeat; + + static struct HasPostblit + { + this(this) {} + } + + auto nonMobile = map!"a"(repeat(HasPostblit.init)); + static assert(!hasMobileElements!(typeof(nonMobile))); + static assert( hasMobileElements!(int[])); + static assert( hasMobileElements!(inout(int)[])); + static assert( hasMobileElements!(typeof(iota(1000)))); + + static assert( hasMobileElements!( string)); + static assert( hasMobileElements!(dstring)); + static assert( hasMobileElements!( char[])); + static assert( hasMobileElements!(dchar[])); +} + +/** +The element type of $(D R). $(D R) does not have to be a range. The +element type is determined as the type yielded by $(D r.front) for an +object $(D r) of type $(D R). For example, $(D ElementType!(T[])) is +$(D T) if $(D T[]) isn't a narrow string; if it is, the element type is +$(D dchar). If $(D R) doesn't have $(D front), $(D ElementType!R) is +$(D void). + */ +template ElementType(R) +{ + static if (is(typeof(R.init.front.init) T)) + alias ElementType = T; + else + alias ElementType = void; +} + +/// +@safe unittest +{ + import std.range : iota; + + // Standard arrays: returns the type of the elements of the array + static assert(is(ElementType!(int[]) == int)); + + // Accessing .front retrieves the decoded dchar + static assert(is(ElementType!(char[]) == dchar)); // rvalue + static assert(is(ElementType!(dchar[]) == dchar)); // lvalue + + // Ditto + static assert(is(ElementType!(string) == dchar)); + static assert(is(ElementType!(dstring) == immutable(dchar))); + + // For ranges it gets the type of .front. + auto range = iota(0, 10); + static assert(is(ElementType!(typeof(range)) == int)); +} + +@safe unittest +{ + static assert(is(ElementType!(byte[]) == byte)); + static assert(is(ElementType!(wchar[]) == dchar)); // rvalue + static assert(is(ElementType!(wstring) == dchar)); +} + +@safe unittest +{ + enum XYZ : string { a = "foo" } + auto x = XYZ.a.front; + immutable char[3] a = "abc"; + int[] i; + void[] buf; + static assert(is(ElementType!(XYZ) == dchar)); + static assert(is(ElementType!(typeof(a)) == dchar)); + static assert(is(ElementType!(typeof(i)) == int)); + static assert(is(ElementType!(typeof(buf)) == void)); + static assert(is(ElementType!(inout(int)[]) == inout(int))); + static assert(is(ElementType!(inout(int[])) == inout(int))); +} + +@safe unittest +{ + static assert(is(ElementType!(int[5]) == int)); + static assert(is(ElementType!(int[0]) == int)); + static assert(is(ElementType!(char[5]) == dchar)); + static assert(is(ElementType!(char[0]) == dchar)); +} + +@safe unittest //11336 +{ + static struct S + { + this(this) @disable; + } + static assert(is(ElementType!(S[]) == S)); +} + +@safe unittest // 11401 +{ + // ElementType should also work for non-@propety 'front' + struct E { ushort id; } + struct R + { + E front() { return E.init; } + } + static assert(is(ElementType!R == E)); +} + +/** +The encoding element type of $(D R). For narrow strings ($(D char[]), +$(D wchar[]) and their qualified variants including $(D string) and +$(D wstring)), $(D ElementEncodingType) is the character type of the +string. For all other types, $(D ElementEncodingType) is the same as +$(D ElementType). + */ +template ElementEncodingType(R) +{ + static if (is(StringTypeOf!R) && is(R : E[], E)) + alias ElementEncodingType = E; + else + alias ElementEncodingType = ElementType!R; +} + +/// +@safe unittest +{ + import std.range : iota; + // internally the range stores the encoded type + static assert(is(ElementEncodingType!(char[]) == char)); + + static assert(is(ElementEncodingType!(wstring) == immutable(wchar))); + + static assert(is(ElementEncodingType!(byte[]) == byte)); + + auto range = iota(0, 10); + static assert(is(ElementEncodingType!(typeof(range)) == int)); +} + +@safe unittest +{ + static assert(is(ElementEncodingType!(wchar[]) == wchar)); + static assert(is(ElementEncodingType!(dchar[]) == dchar)); + static assert(is(ElementEncodingType!(string) == immutable(char))); + static assert(is(ElementEncodingType!(dstring) == immutable(dchar))); + static assert(is(ElementEncodingType!(int[]) == int)); +} + +@safe unittest +{ + enum XYZ : string { a = "foo" } + auto x = XYZ.a.front; + immutable char[3] a = "abc"; + int[] i; + void[] buf; + static assert(is(ElementType!(XYZ) : dchar)); + static assert(is(ElementEncodingType!(char[]) == char)); + static assert(is(ElementEncodingType!(string) == immutable char)); + static assert(is(ElementType!(typeof(a)) : dchar)); + static assert(is(ElementType!(typeof(i)) == int)); + static assert(is(ElementEncodingType!(typeof(i)) == int)); + static assert(is(ElementType!(typeof(buf)) : void)); + + static assert(is(ElementEncodingType!(inout char[]) : inout(char))); +} + +@safe unittest +{ + static assert(is(ElementEncodingType!(int[5]) == int)); + static assert(is(ElementEncodingType!(int[0]) == int)); + static assert(is(ElementEncodingType!(char[5]) == char)); + static assert(is(ElementEncodingType!(char[0]) == char)); +} + +/** +Returns $(D true) if $(D R) is an input range and has swappable +elements. The following code should compile for any range +with swappable elements. + +---- +R r; +static assert(isInputRange!R); +swap(r.front, r.front); +static if (isBidirectionalRange!R) swap(r.back, r.front); +static if (isRandomAccessRange!R) swap(r[], r.front); +---- + */ +template hasSwappableElements(R) +{ + import std.algorithm : swap; + enum bool hasSwappableElements = isInputRange!R && is(typeof( + (inout int = 0) + { + R r = R.init; + swap(r.front, r.front); + static if (isBidirectionalRange!R) swap(r.back, r.front); + static if (isRandomAccessRange!R) swap(r[0], r.front); + })); +} + +/// +@safe unittest +{ + static assert(!hasSwappableElements!(const int[])); + static assert(!hasSwappableElements!(const(int)[])); + static assert(!hasSwappableElements!(inout(int)[])); + static assert( hasSwappableElements!(int[])); + + static assert(!hasSwappableElements!( string)); + static assert(!hasSwappableElements!(dstring)); + static assert(!hasSwappableElements!( char[])); + static assert( hasSwappableElements!(dchar[])); +} + +/** +Returns $(D true) if $(D R) is an input range and has mutable +elements. The following code should compile for any range +with assignable elements. + +---- +R r; +static assert(isInputRange!R); +r.front = r.front; +static if (isBidirectionalRange!R) r.back = r.front; +static if (isRandomAccessRange!R) r[0] = r.front; +---- + */ +template hasAssignableElements(R) +{ + enum bool hasAssignableElements = isInputRange!R && is(typeof( + (inout int = 0) + { + R r = R.init; + r.front = r.front; + static if (isBidirectionalRange!R) r.back = r.front; + static if (isRandomAccessRange!R) r[0] = r.front; + })); +} + +/// +@safe unittest +{ + static assert(!hasAssignableElements!(const int[])); + static assert(!hasAssignableElements!(const(int)[])); + static assert( hasAssignableElements!(int[])); + static assert(!hasAssignableElements!(inout(int)[])); + + static assert(!hasAssignableElements!( string)); + static assert(!hasAssignableElements!(dstring)); + static assert(!hasAssignableElements!( char[])); + static assert( hasAssignableElements!(dchar[])); +} + +/** +Tests whether the range $(D R) has lvalue elements. These are defined as +elements that can be passed by reference and have their address taken. +The following code should compile for any range with lvalue elements. +---- +void passByRef(ref ElementType!R stuff); +... +static assert(isInputRange!R); +passByRef(r.front); +static if (isBidirectionalRange!R) passByRef(r.back); +static if (isRandomAccessRange!R) passByRef(r[0]); +---- +*/ +template hasLvalueElements(R) +{ + enum bool hasLvalueElements = isInputRange!R && is(typeof( + (inout int = 0) + { + void checkRef(ref ElementType!R stuff); + R r = R.init; + + checkRef(r.front); + static if (isBidirectionalRange!R) checkRef(r.back); + static if (isRandomAccessRange!R) checkRef(r[0]); + })); +} + +/// +@safe unittest +{ + import std.range : iota, chain; + + static assert( hasLvalueElements!(int[])); + static assert( hasLvalueElements!(const(int)[])); + static assert( hasLvalueElements!(inout(int)[])); + static assert( hasLvalueElements!(immutable(int)[])); + static assert(!hasLvalueElements!(typeof(iota(3)))); + + static assert(!hasLvalueElements!( string)); + static assert( hasLvalueElements!(dstring)); + static assert(!hasLvalueElements!( char[])); + static assert( hasLvalueElements!(dchar[])); + + auto c = chain([1, 2, 3], [4, 5, 6]); + static assert( hasLvalueElements!(typeof(c))); +} + +@safe unittest +{ + // bugfix 6336 + struct S { immutable int value; } + static assert( isInputRange!(S[])); + static assert( hasLvalueElements!(S[])); +} + +/** +Returns $(D true) if $(D R) has a $(D length) member that returns an +integral type. $(D R) does not have to be a range. Note that $(D +length) is an optional primitive as no range must implement it. Some +ranges do not store their length explicitly, some cannot compute it +without actually exhausting the range (e.g. socket streams), and some +other ranges may be infinite. + +Although narrow string types ($(D char[]), $(D wchar[]), and their +qualified derivatives) do define a $(D length) property, $(D +hasLength) yields $(D false) for them. This is because a narrow +string's length does not reflect the number of characters, but instead +the number of encoding units, and as such is not useful with +range-oriented algorithms. + */ +template hasLength(R) +{ + enum bool hasLength = !isNarrowString!R && is(typeof( + (inout int = 0) + { + R r = R.init; + static assert(is(typeof(r.length) : ulong)); + })); +} + +/// +@safe unittest +{ + static assert(!hasLength!(char[])); + static assert( hasLength!(int[])); + static assert( hasLength!(inout(int)[])); + + struct A { ulong length; } + struct B { size_t length() { return 0; } } + struct C { @property size_t length() { return 0; } } + static assert( hasLength!(A)); + static assert(!hasLength!(B)); + static assert( hasLength!(C)); +} + +/** +Returns $(D true) if $(D R) is an infinite input range. An +infinite input range is an input range that has a statically-defined +enumerated member called $(D empty) that is always $(D false), +for example: + +---- +struct MyInfiniteRange +{ + enum bool empty = false; + ... +} +---- + */ + +template isInfinite(R) +{ + static if (isInputRange!R && __traits(compiles, { enum e = R.empty; })) + enum bool isInfinite = !R.empty; + else + enum bool isInfinite = false; +} + +/// +@safe unittest +{ + import std.range : Repeat; + static assert(!isInfinite!(int[])); + static assert( isInfinite!(Repeat!(int))); +} + +/** +Returns $(D true) if $(D R) offers a slicing operator with integral boundaries +that returns a forward range type. + +For finite ranges, the result of $(D opSlice) must be of the same type as the +original range type. If the range defines $(D opDollar), then it must support +subtraction. + +For infinite ranges, when $(I not) using $(D opDollar), the result of +$(D opSlice) must be the result of $(LREF take) or $(LREF takeExactly) on the +original range (they both return the same type for infinite ranges). However, +when using $(D opDollar), the result of $(D opSlice) must be that of the +original range type. + +The following code must compile for $(D hasSlicing) to be $(D true): + +---- +R r = void; + +static if(isInfinite!R) + typeof(take(r, 1)) s = r[1 .. 2]; +else +{ + static assert(is(typeof(r[1 .. 2]) == R)); + R s = r[1 .. 2]; +} + +s = r[1 .. 2]; + +static if(is(typeof(r[0 .. $]))) +{ + static assert(is(typeof(r[0 .. $]) == R)); + R t = r[0 .. $]; + t = r[0 .. $]; + + static if(!isInfinite!R) + { + static assert(is(typeof(r[0 .. $ - 1]) == R)); + R u = r[0 .. $ - 1]; + u = r[0 .. $ - 1]; + } +} + +static assert(isForwardRange!(typeof(r[1 .. 2]))); +static assert(hasLength!(typeof(r[1 .. 2]))); +---- + */ +template hasSlicing(R) +{ + enum bool hasSlicing = isForwardRange!R && !isNarrowString!R && is(typeof( + (inout int = 0) + { + R r = R.init; + + static if(isInfinite!R) + { + typeof(r[1 .. 1]) s = r[1 .. 2]; + } + else + { + static assert(is(typeof(r[1 .. 2]) == R)); + R s = r[1 .. 2]; + } + + s = r[1 .. 2]; + + static if(is(typeof(r[0 .. $]))) + { + static assert(is(typeof(r[0 .. $]) == R)); + R t = r[0 .. $]; + t = r[0 .. $]; + + static if(!isInfinite!R) + { + static assert(is(typeof(r[0 .. $ - 1]) == R)); + R u = r[0 .. $ - 1]; + u = r[0 .. $ - 1]; + } + } + + static assert(isForwardRange!(typeof(r[1 .. 2]))); + static assert(hasLength!(typeof(r[1 .. 2]))); + })); +} + +/// +@safe unittest +{ + import std.range : takeExactly; + static assert( hasSlicing!(int[])); + static assert( hasSlicing!(const(int)[])); + static assert(!hasSlicing!(const int[])); + static assert( hasSlicing!(inout(int)[])); + static assert(!hasSlicing!(inout int [])); + static assert( hasSlicing!(immutable(int)[])); + static assert(!hasSlicing!(immutable int[])); + static assert(!hasSlicing!string); + static assert( hasSlicing!dstring); + + enum rangeFuncs = "@property int front();" ~ + "void popFront();" ~ + "@property bool empty();" ~ + "@property auto save() { return this; }" ~ + "@property size_t length();"; + + struct A { mixin(rangeFuncs); int opSlice(size_t, size_t); } + struct B { mixin(rangeFuncs); B opSlice(size_t, size_t); } + struct C { mixin(rangeFuncs); @disable this(); C opSlice(size_t, size_t); } + struct D { mixin(rangeFuncs); int[] opSlice(size_t, size_t); } + static assert(!hasSlicing!(A)); + static assert( hasSlicing!(B)); + static assert( hasSlicing!(C)); + static assert(!hasSlicing!(D)); + + struct InfOnes + { + enum empty = false; + void popFront() {} + @property int front() { return 1; } + @property InfOnes save() { return this; } + auto opSlice(size_t i, size_t j) { return takeExactly(this, j - i); } + auto opSlice(size_t i, Dollar d) { return this; } + + struct Dollar {} + Dollar opDollar() const { return Dollar.init; } + } + + static assert(hasSlicing!InfOnes); +} + +/** +This is a best-effort implementation of $(D length) for any kind of +range. + +If $(D hasLength!Range), simply returns $(D range.length) without +checking $(D upTo) (when specified). + +Otherwise, walks the range through its length and returns the number +of elements seen. Performes $(BIGOH n) evaluations of $(D range.empty) +and $(D range.popFront()), where $(D n) is the effective length of $(D +range). + +The $(D upTo) parameter is useful to "cut the losses" in case +the interest is in seeing whether the range has at least some number +of elements. If the parameter $(D upTo) is specified, stops if $(D +upTo) steps have been taken and returns $(D upTo). + +Infinite ranges are compatible, provided the parameter $(D upTo) is +specified, in which case the implementation simply returns upTo. + */ +auto walkLength(Range)(Range range) + if (isInputRange!Range && !isInfinite!Range) +{ + static if (hasLength!Range) + return range.length; + else + { + size_t result; + for ( ; !range.empty ; range.popFront() ) + ++result; + return result; + } +} +/// ditto +auto walkLength(Range)(Range range, const size_t upTo) + if (isInputRange!Range) +{ + static if (hasLength!Range) + return range.length; + else static if (isInfinite!Range) + return upTo; + else + { + size_t result; + for ( ; result < upTo && !range.empty ; range.popFront() ) + ++result; + return result; + } +} + +@safe unittest +{ + import std.algorithm : filter; + import std.range : recurrence, take; + + //hasLength Range + int[] a = [ 1, 2, 3 ]; + assert(walkLength(a) == 3); + assert(walkLength(a, 0) == 3); + assert(walkLength(a, 2) == 3); + assert(walkLength(a, 4) == 3); + + //Forward Range + auto b = filter!"true"([1, 2, 3, 4]); + assert(b.walkLength() == 4); + assert(b.walkLength(0) == 0); + assert(b.walkLength(2) == 2); + assert(b.walkLength(4) == 4); + assert(b.walkLength(6) == 4); + + //Infinite Range + auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1); + assert(!__traits(compiles, fibs.walkLength())); + assert(fibs.take(10).walkLength() == 10); + assert(fibs.walkLength(55) == 55); +} + +/** + Eagerly advances $(D r) itself (not a copy) up to $(D n) times (by + calling $(D r.popFront)). $(D popFrontN) takes $(D r) by $(D ref), + so it mutates the original range. Completes in $(BIGOH 1) steps for ranges + that support slicing and have length. + Completes in $(BIGOH n) time for all other ranges. + + Returns: + How much $(D r) was actually advanced, which may be less than $(D n) if + $(D r) did not have at least $(D n) elements. + + $(D popBackN) will behave the same but instead removes elements from + the back of the (bidirectional) range instead of the front. +*/ +size_t popFrontN(Range)(ref Range r, size_t n) + if (isInputRange!Range) +{ + static if (hasLength!Range) + { + n = cast(size_t) (n < r.length ? n : r.length); + } + + static if (hasSlicing!Range && is(typeof(r = r[n .. $]))) + { + r = r[n .. $]; + } + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + { + r = r[n .. r.length]; + } + else + { + static if (hasLength!Range) + { + foreach (i; 0 .. n) + r.popFront(); + } + else + { + foreach (i; 0 .. n) + { + if (r.empty) return i; + r.popFront(); + } + } + } + return n; +} + +/// ditto +size_t popBackN(Range)(ref Range r, size_t n) + if (isBidirectionalRange!Range) +{ + static if (hasLength!Range) + { + n = cast(size_t) (n < r.length ? n : r.length); + } + + static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n]))) + { + r = r[0 .. $ - n]; + } + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + { + r = r[0 .. r.length - n]; + } + else + { + static if (hasLength!Range) + { + foreach (i; 0 .. n) + r.popBack(); + } + else + { + foreach (i; 0 .. n) + { + if (r.empty) return i; + r.popBack(); + } + } + } + return n; +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4, 5 ]; + a.popFrontN(2); + assert(a == [ 3, 4, 5 ]); + a.popFrontN(7); + assert(a == [ ]); +} + +/// +@safe unittest +{ + import std.algorithm : equal; + import std.range : iota; + auto LL = iota(1L, 7L); + auto r = popFrontN(LL, 2); + assert(equal(LL, [3L, 4L, 5L, 6L])); + assert(r == 2); +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4, 5 ]; + a.popBackN(2); + assert(a == [ 1, 2, 3 ]); + a.popBackN(7); + assert(a == [ ]); +} + +/// +@safe unittest +{ + import std.algorithm : equal; + import std.range : iota; + auto LL = iota(1L, 7L); + auto r = popBackN(LL, 2); + assert(equal(LL, [1L, 2L, 3L, 4L])); + assert(r == 2); +} + +/** + Eagerly advances $(D r) itself (not a copy) exactly $(D n) times (by + calling $(D r.popFront)). $(D popFrontExactly) takes $(D r) by $(D ref), + so it mutates the original range. Completes in $(BIGOH 1) steps for ranges + that support slicing, and have either length or are infinite. + Completes in $(BIGOH n) time for all other ranges. + + Note: Unlike $(LREF popFrontN), $(D popFrontExactly) will assume that the + range holds at least $(D n) elements. This makes $(D popFrontExactly) + faster than $(D popFrontN), but it also means that if $(D range) does + not contain at least $(D n) elements, it will attempt to call $(D popFront) + on an empty range, which is undefined behavior. So, only use + $(D popFrontExactly) when it is guaranteed that $(D range) holds at least + $(D n) elements. + + $(D popBackExactly) will behave the same but instead removes elements from + the back of the (bidirectional) range instead of the front. +*/ +void popFrontExactly(Range)(ref Range r, size_t n) + if (isInputRange!Range) +{ + static if (hasLength!Range) + assert(n <= r.length, "range is smaller than amount of items to pop"); + + static if (hasSlicing!Range && is(typeof(r = r[n .. $]))) + r = r[n .. $]; + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + r = r[n .. r.length]; + else + foreach (i; 0 .. n) + r.popFront(); +} + +/// ditto +void popBackExactly(Range)(ref Range r, size_t n) + if (isBidirectionalRange!Range) +{ + static if (hasLength!Range) + assert(n <= r.length, "range is smaller than amount of items to pop"); + + static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n]))) + r = r[0 .. $ - n]; + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + r = r[0 .. r.length - n]; + else + foreach (i; 0 .. n) + r.popBack(); +} + +/// +@safe unittest +{ + import std.algorithm : filterBidirectional, equal; + + auto a = [1, 2, 3]; + a.popFrontExactly(1); + assert(a == [2, 3]); + a.popBackExactly(1); + assert(a == [2]); + + string s = "日本語"; + s.popFrontExactly(1); + assert(s == "本語"); + s.popBackExactly(1); + assert(s == "本"); + + auto bd = filterBidirectional!"true"([1, 2, 3]); + bd.popFrontExactly(1); + assert(bd.equal([2, 3])); + bd.popBackExactly(1); + assert(bd.equal([2])); +} + +/** + Moves the front of $(D r) out and returns it. Leaves $(D r.front) in a + destroyable state that does not allocate any resources (usually equal + to its $(D .init) value). +*/ +ElementType!R moveFront(R)(R r) +{ + static if (is(typeof(&r.moveFront))) { + return r.moveFront(); + } else static if (!hasElaborateCopyConstructor!(ElementType!R)) { + return r.front; + } else static if (is(typeof(&(r.front())) == ElementType!R*)) { + import std.algorithm : move; + return move(r.front); + } else { + static assert(0, + "Cannot move front of a range with a postblit and an rvalue front."); + } +} + +/// +@safe unittest +{ + auto a = [ 1, 2, 3 ]; + assert(moveFront(a) == 1); + + // define a perfunctory input range + struct InputRange + { + @property bool empty() { return false; } + @property int front() { return 42; } + void popFront() {} + int moveFront() { return 43; } + } + InputRange r; + assert(moveFront(r) == 43); +} + +@safe unittest +{ + struct R + { + @property ref int front() { static int x = 42; return x; } + this(this){} + } + R r; + assert(moveFront(r) == 42); +} + +/** + Moves the back of $(D r) out and returns it. Leaves $(D r.back) in a + destroyable state that does not allocate any resources (usually equal + to its $(D .init) value). +*/ +ElementType!R moveBack(R)(R r) +{ + static if (is(typeof(&r.moveBack))) { + return r.moveBack(); + } else static if (!hasElaborateCopyConstructor!(ElementType!R)) { + return r.back; + } else static if (is(typeof(&(r.back())) == ElementType!R*)) { + import std.algorithm : move; + return move(r.back); + } else { + static assert(0, + "Cannot move back of a range with a postblit and an rvalue back."); + } +} + +/// +@safe unittest +{ + struct TestRange + { + int payload = 5; + @property bool empty() { return false; } + @property TestRange save() { return this; } + @property ref int front() return { return payload; } + @property ref int back() return { return payload; } + void popFront() { } + void popBack() { } + } + static assert(isBidirectionalRange!TestRange); + TestRange r; + auto x = moveBack(r); + assert(x == 5); +} + +/** + Moves element at index $(D i) of $(D r) out and returns it. Leaves $(D + r.front) in a destroyable state that does not allocate any resources + (usually equal to its $(D .init) value). +*/ +ElementType!R moveAt(R, I)(R r, I i) if (isIntegral!I) +{ + static if (is(typeof(&r.moveAt))) { + return r.moveAt(i); + } else static if (!hasElaborateCopyConstructor!(ElementType!(R))) { + return r[i]; + } else static if (is(typeof(&r[i]) == ElementType!R*)) { + import std.algorithm : move; + return move(r[i]); + } else { + static assert(0, + "Cannot move element of a range with a postblit and rvalue elements."); + } +} + +/// +@safe unittest +{ + auto a = [1,2,3,4]; + foreach(idx, it; a) + { + assert(it == moveAt(a, idx)); + } +} + +@safe unittest +{ + import std.internal.test.dummyrange; + + foreach(DummyType; AllDummyRanges) { + auto d = DummyType.init; + assert(moveFront(d) == 1); + + static if (isBidirectionalRange!DummyType) { + assert(moveBack(d) == 10); + } + + static if (isRandomAccessRange!DummyType) { + assert(moveAt(d, 2) == 3); + } + } +} + +/** +Implements the range interface primitive $(D empty) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.empty) is +equivalent to $(D empty(array)). + */ + +@property bool empty(T)(in T[] a) @safe pure nothrow +{ + return !a.length; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + assert(!a.empty); + assert(a[3 .. $].empty); +} + +/** +Implements the range interface primitive $(D save) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.save) is +equivalent to $(D save(array)). The function does not duplicate the +content of the array, it simply returns its argument. + */ + +@property T[] save(T)(T[] a) @safe pure nothrow +{ + return a; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + auto b = a.save; + assert(b is a); +} +/** +Implements the range interface primitive $(D popFront) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.popFront) is +equivalent to $(D popFront(array)). For $(GLOSSARY narrow strings), +$(D popFront) automatically advances to the next $(GLOSSARY code +point). +*/ + +void popFront(T)(ref T[] a) @safe pure nothrow +if (!isNarrowString!(T[]) && !is(T[] == void[])) +{ + assert(a.length, "Attempting to popFront() past the end of an array of " ~ T.stringof); + a = a[1 .. $]; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + a.popFront(); + assert(a == [ 2, 3 ]); +} + +version(unittest) +{ + static assert(!is(typeof({ int[4] a; popFront(a); }))); + static assert(!is(typeof({ immutable int[] a; popFront(a); }))); + static assert(!is(typeof({ void[] a; popFront(a); }))); +} + +// Specialization for narrow strings. The necessity of +void popFront(C)(ref C[] str) @trusted pure nothrow +if (isNarrowString!(C[])) +{ + assert(str.length, "Attempting to popFront() past the end of an array of " ~ C.stringof); + + static if(is(Unqual!C == char)) + { + immutable c = str[0]; + if(c < 0x80) + { + //ptr is used to avoid unnnecessary bounds checking. + str = str.ptr[1 .. str.length]; + } + else + { + import core.bitop : bsr; + auto msbs = 7 - bsr(~c); + if((msbs < 2) | (msbs > 6)) + { + //Invalid UTF-8 + msbs = 1; + } + str = str[msbs .. $]; + } + } + else static if(is(Unqual!C == wchar)) + { + immutable u = str[0]; + str = str[1 + (u >= 0xD800 && u <= 0xDBFF) .. $]; + } + else static assert(0, "Bad template constraint."); +} + +@safe pure unittest +{ + import std.typetuple; + + foreach(S; TypeTuple!(string, wstring, dstring)) + { + S s = "\xC2\xA9hello"; + s.popFront(); + assert(s == "hello"); + + S str = "hello\U00010143\u0100\U00010143"; + foreach(dchar c; ['h', 'e', 'l', 'l', 'o', '\U00010143', '\u0100', '\U00010143']) + { + assert(str.front == c); + str.popFront(); + } + assert(str.empty); + + static assert(!is(typeof({ immutable S a; popFront(a); }))); + static assert(!is(typeof({ typeof(S.init[0])[4] a; popFront(a); }))); + } + + C[] _eatString(C)(C[] str) + { + while(!str.empty) + str.popFront(); + + return str; + } + enum checkCTFE = _eatString("ウェブサイト@La_Verité.com"); + static assert(checkCTFE.empty); + enum checkCTFEW = _eatString("ウェブサイト@La_Verité.com"w); + static assert(checkCTFEW.empty); +} + +/** +Implements the range interface primitive $(D popBack) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.popBack) is +equivalent to $(D popBack(array)). For $(GLOSSARY narrow strings), $(D +popFront) automatically eliminates the last $(GLOSSARY code point). +*/ + +void popBack(T)(ref T[] a) @safe pure nothrow +if (!isNarrowString!(T[]) && !is(T[] == void[])) +{ + assert(a.length); + a = a[0 .. $ - 1]; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + a.popBack(); + assert(a == [ 1, 2 ]); +} + +version(unittest) +{ + static assert(!is(typeof({ immutable int[] a; popBack(a); }))); + static assert(!is(typeof({ int[4] a; popBack(a); }))); + static assert(!is(typeof({ void[] a; popBack(a); }))); +} + +// Specialization for arrays of char +void popBack(T)(ref T[] a) @safe pure +if (isNarrowString!(T[])) +{ + assert(a.length, "Attempting to popBack() past the front of an array of " ~ T.stringof); + a = a[0 .. $ - std.utf.strideBack(a, $)]; +} + +@safe pure unittest +{ + import std.typetuple; + + foreach(S; TypeTuple!(string, wstring, dstring)) + { + S s = "hello\xE2\x89\xA0"; + s.popBack(); + assert(s == "hello"); + S s3 = "\xE2\x89\xA0"; + auto c = s3.back; + assert(c == cast(dchar)'\u2260'); + s3.popBack(); + assert(s3 == ""); + + S str = "\U00010143\u0100\U00010143hello"; + foreach(dchar ch; ['o', 'l', 'l', 'e', 'h', '\U00010143', '\u0100', '\U00010143']) + { + assert(str.back == ch); + str.popBack(); + } + assert(str.empty); + + static assert(!is(typeof({ immutable S a; popBack(a); }))); + static assert(!is(typeof({ typeof(S.init[0])[4] a; popBack(a); }))); + } +} + +/** +Implements the range interface primitive $(D front) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.front) is +equivalent to $(D front(array)). For $(GLOSSARY narrow strings), $(D +front) automatically returns the first $(GLOSSARY code point) as a $(D +dchar). +*/ +@property ref T front(T)(T[] a) @safe pure nothrow +if (!isNarrowString!(T[]) && !is(T[] == void[])) +{ + assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); + return a[0]; +} + +/// +@safe pure nothrow unittest +{ + int[] a = [ 1, 2, 3 ]; + assert(a.front == 1); +} + +@safe pure nothrow unittest +{ + auto a = [ 1, 2 ]; + a.front = 4; + assert(a.front == 4); + assert(a == [ 4, 2 ]); + + immutable b = [ 1, 2 ]; + assert(b.front == 1); + + int[2] c = [ 1, 2 ]; + assert(c.front == 1); +} + +@property dchar front(T)(T[] a) @safe pure if (isNarrowString!(T[])) +{ + import std.utf : decode; + assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); + size_t i = 0; + return decode(a, i); +} + +/** +Implements the range interface primitive $(D back) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.back) is +equivalent to $(D back(array)). For $(GLOSSARY narrow strings), $(D +back) automatically returns the last $(GLOSSARY code point) as a $(D +dchar). +*/ +@property ref T back(T)(T[] a) @safe pure nothrow if (!isNarrowString!(T[])) +{ + assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof); + return a[$ - 1]; +} + +/// +@safe pure nothrow unittest +{ + int[] a = [ 1, 2, 3 ]; + assert(a.back == 3); + a.back += 4; + assert(a.back == 7); +} + +@safe pure nothrow unittest +{ + immutable b = [ 1, 2, 3 ]; + assert(b.back == 3); + + int[3] c = [ 1, 2, 3 ]; + assert(c.back == 3); +} + +// Specialization for strings +@property dchar back(T)(T[] a) @safe pure if (isNarrowString!(T[])) +{ + import std.utf : decode; + assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof); + size_t i = a.length - std.utf.strideBack(a, a.length); + return decode(a, i); +} diff --git a/std/regex.d b/std/regex.d deleted file mode 100644 index c91488f4ecc..00000000000 --- a/std/regex.d +++ /dev/null @@ -1,7570 +0,0 @@ -//Written in the D programming language -/++ - $(SECTION Intro) - $(LUCKY Regular expressions) are a commonly used method of pattern matching - on strings, with $(I regex) being a catchy word for a pattern in this domain - specific language. Typical problems usually solved by regular expressions - include validation of user input and the ubiquitous find & replace - in text processing utilities. - - $(SECTION Synopsis) - --- - import std.regex; - import std.stdio; - void main() - { - // Print out all possible dd/mm/yy(yy) dates found in user input. - auto r = regex(r"\b[0-9][0-9]?/[0-9][0-9]?/[0-9][0-9](?:[0-9][0-9])?\b"); - foreach(line; stdin.byLine) - { - // matchAll() returns a range that can be iterated - // to get all subsequent matches. - foreach(c; matchAll(line, r)) - writeln(c.hit); - } - } - ... - - // Create a static regex at compile-time, which contains fast native code. - auto ctr = ctRegex!(`^.*/([^/]+)/?$`); - - // It works just like a normal regex: - auto c2 = matchFirst("foo/bar", ctr); // First match found here, if any - assert(!c2.empty); // Be sure to check if there is a match before examining contents! - assert(c2[1] == "bar"); // Captures is a range of submatches: 0 = full match. - - ... - - // The result of the $(D matchAll) is directly testable with if/assert/while. - // e.g. test if a string consists of letters: - assert(matchFirst("Letter", `^\p{L}+$`)); - - - --- - $(SECTION Syntax and general information) - The general usage guideline is to keep regex complexity on the side of simplicity, - as its capabilities reside in purely character-level manipulation. - As such it's ill-suited for tasks involving higher level invariants - like matching an integer number $(U bounded) in an [a,b] interval. - Checks of this sort of are better addressed by additional post-processing. - - The basic syntax shouldn't surprise experienced users of regular expressions. - For an introduction to $(D std.regex) see a - $(WEB dlang.org/regular-expression.html, short tour) of the module API - and its abilities. - - There are other web resources on regular expressions to help newcomers, - and a good $(WEB www.regular-expressions.info, reference with tutorial) - can easily be found. - - This library uses a remarkably common ECMAScript syntax flavor - with the following extensions: - $(UL - $(LI Named subexpressions, with Python syntax. ) - $(LI Unicode properties such as Scripts, Blocks and common binary properties e.g Alphabetic, White_Space, Hex_Digit etc.) - $(LI Arbitrary length and complexity lookbehind, including lookahead in lookbehind and vise-versa.) - ) - - $(REG_START Pattern syntax ) - $(I std.regex operates on codepoint level, - 'character' in this table denotes a single Unicode codepoint.) - $(REG_TABLE - $(REG_TITLE Pattern element, Semantics ) - $(REG_TITLE Atoms, Match single characters ) - $(REG_ROW any character except [{|*+?()^$, Matches the character itself. ) - $(REG_ROW ., In single line mode matches any character. - Otherwise it matches any character except '\n' and '\r'. ) - $(REG_ROW [class], Matches a single character - that belongs to this character class. ) - $(REG_ROW [^class], Matches a single character that - does $(U not) belong to this character class.) - $(REG_ROW \cC, Matches the control character corresponding to letter C) - $(REG_ROW \xXX, Matches a character with hexadecimal value of XX. ) - $(REG_ROW \uXXXX, Matches a character with hexadecimal value of XXXX. ) - $(REG_ROW \U00YYYYYY, Matches a character with hexadecimal value of YYYYYY. ) - $(REG_ROW \f, Matches a formfeed character. ) - $(REG_ROW \n, Matches a linefeed character. ) - $(REG_ROW \r, Matches a carriage return character. ) - $(REG_ROW \t, Matches a tab character. ) - $(REG_ROW \v, Matches a vertical tab character. ) - $(REG_ROW \d, Matches any Unicode digit. ) - $(REG_ROW \D, Matches any character except Unicode digits. ) - $(REG_ROW \w, Matches any word character (note: this includes numbers).) - $(REG_ROW \W, Matches any non-word character.) - $(REG_ROW \s, Matches whitespace, same as \p{White_Space}.) - $(REG_ROW \S, Matches any character except those recognized as $(I \s ). ) - $(REG_ROW \\, Matches \ character. ) - $(REG_ROW \c where c is one of [|*+?(), Matches the character c itself. ) - $(REG_ROW \p{PropertyName}, Matches a character that belongs - to the Unicode PropertyName set. - Single letter abbreviations can be used without surrounding {,}. ) - $(REG_ROW \P{PropertyName}, Matches a character that does not belong - to the Unicode PropertyName set. - Single letter abbreviations can be used without surrounding {,}. ) - $(REG_ROW \p{InBasicLatin}, Matches any character that is part of - the BasicLatin Unicode $(U block).) - $(REG_ROW \P{InBasicLatin}, Matches any character except ones in - the BasicLatin Unicode $(U block).) - $(REG_ROW \p{Cyrillic}, Matches any character that is part of - Cyrillic $(U script).) - $(REG_ROW \P{Cyrillic}, Matches any character except ones in - Cyrillic $(U script).) - $(REG_TITLE Quantifiers, Specify repetition of other elements) - $(REG_ROW *, Matches previous character/subexpression 0 or more times. - Greedy version - tries as many times as possible.) - $(REG_ROW *?, Matches previous character/subexpression 0 or more times. - Lazy version - stops as early as possible.) - $(REG_ROW +, Matches previous character/subexpression 1 or more times. - Greedy version - tries as many times as possible.) - $(REG_ROW +?, Matches previous character/subexpression 1 or more times. - Lazy version - stops as early as possible.) - $(REG_ROW {n}, Matches previous character/subexpression exactly n times. ) - $(REG_ROW {n,}, Matches previous character/subexpression n times or more. - Greedy version - tries as many times as possible. ) - $(REG_ROW {n,}?, Matches previous character/subexpression n times or more. - Lazy version - stops as early as possible.) - $(REG_ROW {n,m}, Matches previous character/subexpression n to m times. - Greedy version - tries as many times as possible, but no more than m times. ) - $(REG_ROW {n,m}?, Matches previous character/subexpression n to m times. - Lazy version - stops as early as possible, but no less then n times.) - $(REG_TITLE Other, Subexpressions & alternations ) - $(REG_ROW (regex), Matches subexpression regex, - saving matched portion of text for later retrieval. ) - $(REG_ROW (?:regex), Matches subexpression regex, - $(U not) saving matched portion of text. Useful to speed up matching. ) - $(REG_ROW A|B, Matches subexpression A, or failing that, matches B. ) - $(REG_ROW (?P<name>regex), Matches named subexpression - regex labeling it with name 'name'. - When referring to a matched portion of text, - names work like aliases in addition to direct numbers. - ) - $(REG_TITLE Assertions, Match position rather than character ) - $(REG_ROW ^, Matches at the begining of input or line (in multiline mode).) - $(REG_ROW $, Matches at the end of input or line (in multiline mode). ) - $(REG_ROW \b, Matches at word boundary. ) - $(REG_ROW \B, Matches when $(U not) at word boundary. ) - $(REG_ROW (?=regex), Zero-width lookahead assertion. - Matches at a point where the subexpression - regex could be matched starting from the current position. - ) - $(REG_ROW (?!regex), Zero-width negative lookahead assertion. - Matches at a point where the subexpression - regex could $(U not) be matched starting from the current position. - ) - $(REG_ROW (?<=regex), Zero-width lookbehind assertion. Matches at a point - where the subexpression regex could be matched ending - at the current position (matching goes backwards). - ) - $(REG_ROW (? $0 - REG_START =

$0

- SECTION =

$0

- S_LINK = $+ - +/ - -module std.regex; - -import std.array, std.algorithm, std.range, - std.conv, std.exception, std.traits, std.typetuple, - std.uni, std.utf, std.format, std.typecons, std.bitmanip, - std.functional, std.exception; - -import core.bitop, core.stdc.string, core.stdc.stdlib; -static import std.ascii; -import std.string : representation; - -debug(std_regex_parser) import std.stdio; //trace parser progress -debug(std_regex_search) import std.stdio; //trace prefix search engine -debug(std_regex_matcher) import std.stdio; //trace matcher engine -debug(std_regex_allocation) import std.stdio; //track object life cycle -debug(std_regex_ctr) import std.stdio; //dump ctRegex generated sources -debug(std_regex_test) import std.stdio; //trace test suite progress - -private: - -// IR bit pattern: 0b1_xxxxx_yy -// where yy indicates class of instruction, xxxxx for actual operation code -// 00: atom, a normal instruction -// 01: open, opening of a group, has length of contained IR in the low bits -// 10: close, closing of a group, has length of contained IR in the low bits -// 11 unused -// -// Loops with Q (non-greedy, with ? mark) must have the same size / other properties as non Q version -// Possible changes: -//* merge group, option, infinite/repeat start (to never copy during parsing of (a|b){1,2}) -//* reorganize groups to make n args easier to find, or simplify the check for groups of similar ops -// (like lookaround), or make it easier to identify hotspots. - -enum IR:uint { - Char = 0b1_00000_00, //a character - Any = 0b1_00001_00, //any character - CodepointSet = 0b1_00010_00, //a most generic CodepointSet [...] - Trie = 0b1_00011_00, //CodepointSet implemented as Trie - //match with any of a consecutive OrChar's in this sequence - //(used for case insensitive match) - //OrChar holds in upper two bits of data total number of OrChars in this _sequence_ - //the drawback of this representation is that it is difficult - // to detect a jump in the middle of it - OrChar = 0b1_00100_00, - Nop = 0b1_00101_00, //no operation (padding) - End = 0b1_00110_00, //end of program - Bol = 0b1_00111_00, //beginning of a string ^ - Eol = 0b1_01000_00, //end of a string $ - Wordboundary = 0b1_01001_00, //boundary of a word - Notwordboundary = 0b1_01010_00, //not a word boundary - Backref = 0b1_01011_00, //backreference to a group (that has to be pinned, i.e. locally unique) (group index) - GroupStart = 0b1_01100_00, //start of a group (x) (groupIndex+groupPinning(1bit)) - GroupEnd = 0b1_01101_00, //end of a group (x) (groupIndex+groupPinning(1bit)) - Option = 0b1_01110_00, //start of an option within an alternation x | y (length) - GotoEndOr = 0b1_01111_00, //end of an option (length of the rest) - //... any additional atoms here - - OrStart = 0b1_00000_01, //start of alternation group (length) - OrEnd = 0b1_00000_10, //end of the or group (length,mergeIndex) - //with this instruction order - //bit mask 0b1_00001_00 could be used to test/set greediness - InfiniteStart = 0b1_00001_01, //start of an infinite repetition x* (length) - InfiniteEnd = 0b1_00001_10, //end of infinite repetition x* (length,mergeIndex) - InfiniteQStart = 0b1_00010_01, //start of a non eager infinite repetition x*? (length) - InfiniteQEnd = 0b1_00010_10, //end of non eager infinite repetition x*? (length,mergeIndex) - RepeatStart = 0b1_00011_01, //start of a {n,m} repetition (length) - RepeatEnd = 0b1_00011_10, //end of x{n,m} repetition (length,step,minRep,maxRep) - RepeatQStart = 0b1_00100_01, //start of a non eager x{n,m}? repetition (length) - RepeatQEnd = 0b1_00100_10, //end of non eager x{n,m}? repetition (length,step,minRep,maxRep) - // - LookaheadStart = 0b1_00101_01, //begin of the lookahead group (length) - LookaheadEnd = 0b1_00101_10, //end of a lookahead group (length) - NeglookaheadStart = 0b1_00110_01, //start of a negative lookahead (length) - NeglookaheadEnd = 0b1_00110_10, //end of a negative lookahead (length) - LookbehindStart = 0b1_00111_01, //start of a lookbehind (length) - LookbehindEnd = 0b1_00111_10, //end of a lookbehind (length) - NeglookbehindStart= 0b1_01000_01, //start of a negative lookbehind (length) - NeglookbehindEnd = 0b1_01000_10, //end of negative lookbehind (length) -} - -//a shorthand for IR length - full length of specific opcode evaluated at compile time -template IRL(IR code) -{ - enum uint IRL = lengthOfIR(code); -} - -static assert (IRL!(IR.LookaheadStart) == 3); - -//how many parameters follow the IR, should be optimized fixing some IR bits -int immediateParamsIR(IR i){ - switch (i){ - case IR.OrEnd,IR.InfiniteEnd,IR.InfiniteQEnd: - return 1; - case IR.RepeatEnd, IR.RepeatQEnd: - return 4; - case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: - return 2; - default: - return 0; - } -} - -//full length of IR instruction inlcuding all parameters that might follow it -int lengthOfIR(IR i) -{ - return 1 + immediateParamsIR(i); -} - -//full length of the paired IR instruction inlcuding all parameters that might follow it -int lengthOfPairedIR(IR i) -{ - return 1 + immediateParamsIR(pairedIR(i)); -} - -//if the operation has a merge point (this relies on the order of the ops) -bool hasMerge(IR i) -{ - return (i&0b11)==0b10 && i <= IR.RepeatQEnd; -} - -//is an IR that opens a "group" -bool isStartIR(IR i) -{ - return (i&0b11)==0b01; -} - -//is an IR that ends a "group" -bool isEndIR(IR i) -{ - return (i&0b11)==0b10; -} - -//is a standalone IR -bool isAtomIR(IR i) -{ - return (i&0b11)==0b00; -} - -//makes respective pair out of IR i, swapping start/end bits of instruction -IR pairedIR(IR i) -{ - assert(isStartIR(i) || isEndIR(i)); - return cast(IR)(i ^ 0b11); -} - -//encoded IR instruction -struct Bytecode -{ - uint raw; - //natural constraints - enum maxSequence = 2+4; - enum maxData = 1<<22; - enum maxRaw = 1<<31; - - this(IR code, uint data) - { - assert(data < (1<<22) && code < 256); - raw = code<<24 | data; - } - - this(IR code, uint data, uint seq) - { - assert(data < (1<<22) && code < 256 ); - assert(seq >= 2 && seq < maxSequence); - raw = code << 24 | (seq - 2)<<22 | data; - } - - //store raw data - static Bytecode fromRaw(uint data) - { - Bytecode t; - t.raw = data; - return t; - } - - //bit twiddling helpers - //0-arg template due to @@@BUG@@@ 10985 - @property uint data()() const { return raw & 0x003f_ffff; } - - //ditto - //0-arg template due to @@@BUG@@@ 10985 - @property uint sequence()() const { return 2 + (raw >> 22 & 0x3); } - - //ditto - //0-arg template due to @@@BUG@@@ 10985 - @property IR code()() const { return cast(IR)(raw>>24); } - - //ditto - @property bool hotspot() const { return hasMerge(code); } - - //test the class of this instruction - @property bool isAtom() const { return isAtomIR(code); } - - //ditto - @property bool isStart() const { return isStartIR(code); } - - //ditto - @property bool isEnd() const { return isEndIR(code); } - - //number of arguments for this instruction - @property int args() const { return immediateParamsIR(code); } - - //mark this GroupStart or GroupEnd as referenced in backreference - void setBackrefence() - { - assert(code == IR.GroupStart || code == IR.GroupEnd); - raw = raw | 1 << 23; - } - - //is referenced - @property bool backreference() const - { - assert(code == IR.GroupStart || code == IR.GroupEnd); - return cast(bool)(raw & 1 << 23); - } - - //mark as local reference (for backrefs in lookarounds) - void setLocalRef() - { - assert(code == IR.Backref); - raw = raw | 1 << 23; - } - - //is a local ref - @property bool localRef() const - { - assert(code == IR.Backref); - return cast(bool)(raw & 1 << 23); - } - - //human readable name of instruction - @trusted @property string mnemonic() const - {//@@@BUG@@@ to is @system - return to!string(code); - } - - //full length of instruction - @property uint length() const - { - return lengthOfIR(code); - } - - //full length of respective start/end of this instruction - @property uint pairedLength() const - { - return lengthOfPairedIR(code); - } - - //returns bytecode of paired instruction (assuming this one is start or end) - @property Bytecode paired() const - {//depends on bit and struct layout order - assert(isStart || isEnd); - return Bytecode.fromRaw(raw ^ 0b11 << 24); - } - - //gets an index into IR block of the respective pair - uint indexOfPair(uint pc) const - { - assert(isStart || isEnd); - return isStart ? pc + data + length : pc - data - lengthOfPairedIR(code); - } -} - -static assert(Bytecode.sizeof == 4); - -//debugging tool, prints out instruction along with opcodes -@trusted string disassemble(in Bytecode[] irb, uint pc, in NamedGroup[] dict=[]) -{ - auto output = appender!string(); - formattedWrite(output,"%s", irb[pc].mnemonic); - switch(irb[pc].code) - { - case IR.Char: - formattedWrite(output, " %s (0x%x)",cast(dchar)irb[pc].data, irb[pc].data); - break; - case IR.OrChar: - formattedWrite(output, " %s (0x%x) seq=%d", cast(dchar)irb[pc].data, irb[pc].data, irb[pc].sequence); - break; - case IR.RepeatStart, IR.InfiniteStart, IR.Option, IR.GotoEndOr, IR.OrStart: - //forward-jump instructions - uint len = irb[pc].data; - formattedWrite(output, " pc=>%u", pc+len+IRL!(IR.RepeatStart)); - break; - case IR.RepeatEnd, IR.RepeatQEnd: //backward-jump instructions - uint len = irb[pc].data; - formattedWrite(output, " pc=>%u min=%u max=%u step=%u", - pc - len, irb[pc + 3].raw, irb[pc + 4].raw, irb[pc + 2].raw); - break; - case IR.InfiniteEnd, IR.InfiniteQEnd, IR.OrEnd: //ditto - uint len = irb[pc].data; - formattedWrite(output, " pc=>%u", pc-len); - break; - case IR.LookaheadEnd, IR.NeglookaheadEnd: //ditto - uint len = irb[pc].data; - formattedWrite(output, " pc=>%u", pc-len); - break; - case IR.GroupStart, IR.GroupEnd: - uint n = irb[pc].data; - string name; - foreach(v;dict) - if(v.group == n) - { - name = "'"~v.name~"'"; - break; - } - formattedWrite(output, " %s #%u " ~ (irb[pc].backreference ? "referenced" : ""), - name, n); - break; - case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: - uint len = irb[pc].data; - uint start = irb[pc+1].raw, end = irb[pc+2].raw; - formattedWrite(output, " pc=>%u [%u..%u]", pc + len + IRL!(IR.LookaheadStart), start, end); - break; - case IR.Backref: case IR.CodepointSet: case IR.Trie: - uint n = irb[pc].data; - formattedWrite(output, " %u", n); - if(irb[pc].code == IR.Backref) - formattedWrite(output, " %s", irb[pc].localRef ? "local" : "global"); - break; - default://all data-free instructions - } - if(irb[pc].hotspot) - formattedWrite(output, " Hotspot %u", irb[pc+1].raw); - return output.data; -} - -//disassemble the whole chunk -@trusted void printBytecode()(in Bytecode[] slice, in NamedGroup[] dict=[]) -{ - import std.stdio; - for(uint pc=0; pc number of submatch -struct NamedGroup -{ - string name; - uint group; -} - -//holds pair of start-end markers for a submatch -struct Group(DataIndex) -{ - DataIndex begin, end; - @trusted string toString() const - { - auto a = appender!string(); - formattedWrite(a, "%s..%s", begin, end); - return a.data; - } -} - -@trusted void reverseBytecode()(Bytecode[] code) -{ - Bytecode[] rev = new Bytecode[code.length]; - uint revPc = cast(uint)rev.length; - Stack!(Tuple!(uint, uint, uint)) stack; - uint start = 0; - uint end = cast(uint)code.length; - for(;;) - { - for(uint pc = start; pc < end; ) - { - uint len = code[pc].length; - if(code[pc].code == IR.GotoEndOr) - break; //pick next alternation branch - if(code[pc].isAtom) - { - rev[revPc - len .. revPc] = code[pc .. pc + len]; - revPc -= len; - pc += len; - } - else if(code[pc].isStart || code[pc].isEnd) - { - //skip over other embedded lookbehinds they are reversed - if(code[pc].code == IR.LookbehindStart - || code[pc].code == IR.NeglookbehindStart) - { - uint blockLen = len + code[pc].data - + code[pc].pairedLength; - rev[revPc - blockLen .. revPc] = code[pc .. pc + blockLen]; - pc += blockLen; - revPc -= blockLen; - continue; - } - uint second = code[pc].indexOfPair(pc); - uint secLen = code[second].length; - rev[revPc - secLen .. revPc] = code[second .. second + secLen]; - revPc -= secLen; - if(code[pc].code == IR.OrStart) - { - //we pass len bytes forward, but secLen in reverse - uint revStart = revPc - (second + len - secLen - pc); - uint r = revStart; - uint i = pc + IRL!(IR.OrStart); - while(code[i].code == IR.Option) - { - if(code[i - 1].code != IR.OrStart) - { - assert(code[i - 1].code == IR.GotoEndOr); - rev[r - 1] = code[i - 1]; - } - rev[r] = code[i]; - auto newStart = i + IRL!(IR.Option); - auto newEnd = newStart + code[i].data; - auto newRpc = r + code[i].data + IRL!(IR.Option); - if(code[newEnd].code != IR.OrEnd) - { - newRpc--; - } - stack.push(tuple(newStart, newEnd, newRpc)); - r += code[i].data + IRL!(IR.Option); - i += code[i].data + IRL!(IR.Option); - } - pc = i; - revPc = revStart; - assert(code[pc].code == IR.OrEnd); - } - else - pc += len; - } - } - if(stack.empty) - break; - start = stack.top[0]; - end = stack.top[1]; - revPc = stack.top[2]; - stack.pop(); - } - code[] = rev[]; -} - - -//Regular expression engine/parser options: -// global - search all nonoverlapping matches in input -// casefold - case insensitive matching, do casefolding on match in unicode mode -// freeform - ignore whitespace in pattern, to match space use [ ] or \s -// multiline - switch ^, $ detect start and end of linesinstead of just start and end of input -enum RegexOption: uint { - global = 0x1, - casefold = 0x2, - freeform = 0x4, - nonunicode = 0x8, - multiline = 0x10, - singleline = 0x20 -} -//do not reorder this list -alias RegexOptionNames = TypeTuple!('g', 'i', 'x', 'U', 'm', 's'); -static assert( RegexOption.max < 0x80); -enum RegexInfo : uint { oneShot = 0x80 } -alias Escapables = TypeTuple!('[', ']', '\\', '^', '$', '.', '|', '?', ',', '-', - ';', ':', '#', '&', '%', '/', '<', '>', '`', '*', '+', '(', ')', '{', '}', '~'); - -private enum NEL = '\u0085', LS = '\u2028', PS = '\u2029'; - -//test if a given string starts with hex number of maxDigit that's a valid codepoint -//returns it's value and skips these maxDigit chars on success, throws on failure -dchar parseUniHex(Char)(ref Char[] str, size_t maxDigit) -{ - //std.conv.parse is both @system and bogus - enforce(str.length >= maxDigit,"incomplete escape sequence"); - uint val; - for(int k = 0; k < maxDigit; k++) - { - auto current = str[k];//accepts ascii only, so it's OK to index directly - if('0' <= current && current <= '9') - val = val * 16 + current - '0'; - else if('a' <= current && current <= 'f') - val = val * 16 + current -'a' + 10; - else if('A' <= current && current <= 'F') - val = val * 16 + current - 'A' + 10; - else - throw new Exception("invalid escape sequence"); - } - enforce(val <= 0x10FFFF, "invalid codepoint"); - str = str[maxDigit..$]; - return val; -} - -@system unittest //BUG canFind is system -{ - string[] non_hex = [ "000j", "000z", "FffG", "0Z"]; - string[] hex = [ "01", "ff", "00af", "10FFFF" ]; - int[] value = [ 1, 0xFF, 0xAF, 0x10FFFF ]; - foreach(v; non_hex) - assert(collectException(parseUniHex(v, v.length)).msg - .canFind("invalid escape sequence")); - foreach(i, v; hex) - assert(parseUniHex(v, v.length) == value[i]); - string over = "0011FFFF"; - assert(collectException(parseUniHex(over, over.length)).msg - .canFind("invalid codepoint")); -} - -//heuristic value determines maximum CodepointSet length suitable for linear search -enum maxCharsetUsed = 6; - -enum maxCachedTries = 8; - -alias CodepointSetTrie!(13, 8) Trie; -alias codepointSetTrie!(13, 8) makeTrie; - -Trie[CodepointSet] trieCache; - -//accessor with caching -@trusted Trie getTrie(CodepointSet set) -{// @@@BUG@@@ 6357 almost all properties of AA are not @safe - if(__ctfe || maxCachedTries == 0) - return makeTrie(set); - else - { - auto p = set in trieCache; - if(p) - return *p; - if(trieCache.length == maxCachedTries) - { - // flush entries in trieCache - trieCache = null; - } - return (trieCache[set] = makeTrie(set)); - } -} - -//property for \w character class -@property CodepointSet wordCharacter() -{ - return memoizeExpr!("unicode.Alphabetic | unicode.Mn | unicode.Mc - | unicode.Me | unicode.Nd | unicode.Pc")(); -} - -@property Trie wordTrie() -{ - return memoizeExpr!("makeTrie(wordCharacter)")(); -} - -@trusted auto memoizeExpr(string expr)() -{ - if(__ctfe) - return mixin(expr); - alias T = typeof(mixin(expr)); - static T slot; - static bool initialized; - if(!initialized) - { - slot = mixin(expr); - initialized = true; - } - return slot; -} - -auto caseEnclose(CodepointSet set) -{ - auto cased = set & unicode.LC; - foreach (dchar ch; cased.byCodepoint) - { - foreach(c; simpleCaseFoldings(ch)) - set |= c; - } - return set; -} - -/+ - fetch codepoint set corresponding to a name (InBlock or binary property) -+/ -@trusted CodepointSet getUnicodeSet(in char[] name, bool negated, bool casefold) -{ - CodepointSet s = unicode(name); - //FIXME: caseEnclose for new uni as Set | CaseEnclose(SET && LC) - if(casefold) - s = caseEnclose(s); - if(negated) - s = s.inverted; - return s; -} - -//basic stack, just in case it gets used anywhere else then Parser -@trusted struct Stack(T) -{ - T[] data; - @property bool empty(){ return data.empty; } - - @property size_t length(){ return data.length; } - - void push(T val){ data ~= val; } - - T pop() - { - assert(!empty); - auto val = data[$ - 1]; - data = data[0 .. $ - 1]; - if(!__ctfe) - cast(void)data.assumeSafeAppend(); - return val; - } - - @property ref T top() - { - assert(!empty); - return data[$ - 1]; - } -} - -//safety limits -enum maxGroupNumber = 2^^19; -enum maxLookaroundDepth = 16; -// *Bytecode.sizeof, i.e. 1Mb of bytecode alone -enum maxCompiledLength = 2^^18; -//amounts to up to 4 Mb of auxilary table for matching -enum maxCumulativeRepetitionLength = 2^^20; - -alias BasicElementOf(Range) = Unqual!(ElementEncodingType!Range); - -struct Parser(R) - if (isForwardRange!R && is(ElementType!R : dchar)) -{ - enum infinite = ~0u; - dchar _current; - bool empty; - R pat, origin; //keep full pattern for pretty printing error messages - Bytecode[] ir; //resulting bytecode - uint re_flags = 0; //global flags e.g. multiline + internal ones - Stack!(uint) fixupStack; //stack of opened start instructions - NamedGroup[] dict; //maps name -> user group number - //current num of group, group nesting level and repetitions step - Stack!(uint) groupStack; - uint nesting = 0; - uint lookaroundNest = 0; - uint counterDepth = 0; //current depth of nested counted repetitions - CodepointSet[] charsets; // - const(Trie)[] tries; // - uint[] backrefed; //bitarray for groups - - @trusted this(S)(R pattern, S flags) - if(isSomeString!S) - { - pat = origin = pattern; - //reserve slightly more then avg as sampled from unittests - if(!__ctfe) - ir.reserve((pat.length*5+2)/4); - parseFlags(flags); - _current = ' ';//a safe default for freeform parsing - next(); - try - { - parseRegex(); - } - catch(Exception e) - { - error(e.msg);//also adds pattern location - } - put(Bytecode(IR.End, 0)); - } - - //mark referenced groups for latter processing - void markBackref(uint n) - { - if(n/32 >= backrefed.length) - backrefed.length = n/32 + 1; - backrefed[n / 32] |= 1 << (n & 31); - } - - bool isOpenGroup(uint n) - { - // walk the fixup stack and see if there are groups labeled 'n' - // fixup '0' is reserved for alternations - return fixupStack.data[1..$]. - canFind!(fix => ir[fix].code == IR.GroupStart && ir[fix].data == n)(); - } - - @property dchar current(){ return _current; } - - bool _next() - { - if(pat.empty) - { - empty = true; - return false; - } - _current = pat.front; - pat.popFront(); - return true; - } - - void skipSpace() - { - while(isWhite(current) && _next()){ } - } - - bool next() - { - if(re_flags & RegexOption.freeform) - { - bool r = _next(); - skipSpace(); - return r; - } - else - return _next(); - } - - void put(Bytecode code) - { - enforce(ir.length < maxCompiledLength, - "maximum compiled pattern length is exceeded"); - ir ~= code; - } - - void putRaw(uint number) - { - enforce(ir.length < maxCompiledLength, - "maximum compiled pattern length is exceeded"); - ir ~= Bytecode.fromRaw(number); - } - - //parsing number with basic overflow check - uint parseDecimal() - { - uint r = 0; - while(std.ascii.isDigit(current)) - { - if(r >= (uint.max/10)) - error("Overflow in decimal number"); - r = 10*r + cast(uint)(current-'0'); - if(!next()) - break; - } - return r; - } - - //parse control code of form \cXXX, c assumed to be the current symbol - dchar parseControlCode() - { - enforce(next(), "Unfinished escape sequence"); - enforce(('a' <= current && current <= 'z') || ('A' <= current && current <= 'Z'), - "Only letters are allowed after \\c"); - return current & 0x1f; - } - - // - @trusted void parseFlags(S)(S flags) - {//@@@BUG@@@ text is @system - foreach(ch; flags)//flags are ASCII anyway - { - L_FlagSwitch: - switch(ch) - { - - foreach(i, op; __traits(allMembers, RegexOption)) - { - case RegexOptionNames[i]: - if(re_flags & mixin("RegexOption."~op)) - throw new RegexException(text("redundant flag specified: ",ch)); - re_flags |= mixin("RegexOption."~op); - break L_FlagSwitch; - } - default: - throw new RegexException(text("unknown regex flag '",ch,"'")); - } - } - } - - //parse and store IR for regex pattern - @trusted void parseRegex() - { - fixupStack.push(0); - groupStack.push(1);//0 - whole match - auto maxCounterDepth = counterDepth; - uint fix;//fixup pointer - - while(!empty) - { - debug(std_regex_parser) - writeln("*LR*\nSource: ", pat, "\nStack: ",fixupStack.stack.data); - switch(current) - { - case '(': - next(); - nesting++; - uint nglob; - fixupStack.push(cast(uint)ir.length); - if(current == '?') - { - next(); - switch(current) - { - case ':': - put(Bytecode(IR.Nop, 0)); - next(); - break; - case '=': - genLookaround(IR.LookaheadStart); - next(); - break; - case '!': - genLookaround(IR.NeglookaheadStart); - next(); - break; - case 'P': - next(); - if(current != '<') - error("Expected '<' in named group"); - string name; - if(!next() || !(isAlpha(current) || current == '_')) - error("Expected alpha starting a named group"); - name ~= current; - while(next() && (isAlpha(current) || - current == '_' || std.ascii.isDigit(current))) - { - name ~= current; - } - if(current != '>') - error("Expected '>' closing named group"); - next(); - nglob = groupStack.top++; - enforce(groupStack.top <= maxGroupNumber, "limit on submatches is exceeded"); - auto t = NamedGroup(name, nglob); - auto d = assumeSorted!"a.name < b.name"(dict); - auto ind = d.lowerBound(t).length; - insertInPlace(dict, ind, t); - put(Bytecode(IR.GroupStart, nglob)); - break; - case '<': - next(); - if(current == '=') - genLookaround(IR.LookbehindStart); - else if(current == '!') - genLookaround(IR.NeglookbehindStart); - else - error("'!' or '=' expected after '<'"); - next(); - break; - default: - error(" ':', '=', '<', 'P' or '!' expected after '(?' "); - } - } - else - { - nglob = groupStack.top++; - enforce(groupStack.top <= maxGroupNumber, "limit on number of submatches is exceeded"); - put(Bytecode(IR.GroupStart, nglob)); - } - break; - case ')': - enforce(nesting, "Unmatched ')'"); - nesting--; - next(); - fix = fixupStack.pop(); - switch(ir[fix].code) - { - case IR.GroupStart: - put(Bytecode(IR.GroupEnd,ir[fix].data)); - parseQuantifier(fix); - break; - case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: - assert(lookaroundNest); - fixLookaround(fix); - lookaroundNest--; - break; - case IR.Option: //| xxx ) - //two fixups: last option + full OR - finishAlternation(fix); - fix = fixupStack.top; - switch(ir[fix].code) - { - case IR.GroupStart: - fixupStack.pop(); - put(Bytecode(IR.GroupEnd,ir[fix].data)); - parseQuantifier(fix); - break; - case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: - assert(lookaroundNest); - lookaroundNest--; - fix = fixupStack.pop(); - fixLookaround(fix); - break; - default://(?:xxx) - fixupStack.pop(); - parseQuantifier(fix); - } - break; - default://(?:xxx) - parseQuantifier(fix); - } - break; - case '|': - next(); - fix = fixupStack.top; - if(ir.length > fix && ir[fix].code == IR.Option) - { - ir[fix] = Bytecode(ir[fix].code, cast(uint)ir.length - fix); - put(Bytecode(IR.GotoEndOr, 0)); - fixupStack.top = cast(uint)ir.length; //replace latest fixup for Option - put(Bytecode(IR.Option, 0)); - break; - } - uint len, orStart; - //start a new option - if(fixupStack.length == 1) - {//only root entry, effectively no fixup - len = cast(uint)ir.length + IRL!(IR.GotoEndOr); - orStart = 0; - } - else - {//IR.lookahead, etc. fixups that have length > 1, thus check ir[x].length - len = cast(uint)ir.length - fix - (ir[fix].length - 1); - orStart = fix + ir[fix].length; - } - insertInPlace(ir, orStart, Bytecode(IR.OrStart, 0), Bytecode(IR.Option, len)); - assert(ir[orStart].code == IR.OrStart); - put(Bytecode(IR.GotoEndOr, 0)); - fixupStack.push(orStart); //fixup for StartOR - fixupStack.push(cast(uint)ir.length); //for second Option - put(Bytecode(IR.Option, 0)); - break; - default://no groups or whatever - uint start = cast(uint)ir.length; - parseAtom(); - parseQuantifier(start); - } - } - - if(fixupStack.length != 1) - { - fix = fixupStack.pop(); - enforce(ir[fix].code == IR.Option, "no matching ')'"); - finishAlternation(fix); - enforce(fixupStack.length == 1, "no matching ')'"); - } - } - - //helper function, finalizes IR.Option, fix points to the first option of sequence - void finishAlternation(uint fix) - { - enforce(ir[fix].code == IR.Option, "no matching ')'"); - ir[fix] = Bytecode(ir[fix].code, cast(uint)ir.length - fix - IRL!(IR.OrStart)); - fix = fixupStack.pop(); - enforce(ir[fix].code == IR.OrStart, "no matching ')'"); - ir[fix] = Bytecode(IR.OrStart, cast(uint)ir.length - fix - IRL!(IR.OrStart)); - put(Bytecode(IR.OrEnd, cast(uint)ir.length - fix - IRL!(IR.OrStart))); - uint pc = fix + IRL!(IR.OrStart); - while(ir[pc].code == IR.Option) - { - pc = pc + ir[pc].data; - if(ir[pc].code != IR.GotoEndOr) - break; - ir[pc] = Bytecode(IR.GotoEndOr, cast(uint)(ir.length - pc - IRL!(IR.OrEnd))); - pc += IRL!(IR.GotoEndOr); - } - put(Bytecode.fromRaw(0)); - } - - //parse and store IR for atom-quantifier pair - @trusted void parseQuantifier(uint offset) - {//copy is @system - uint replace = ir[offset].code == IR.Nop; - if(empty && !replace) - return; - uint min, max; - switch(current) - { - case '*': - min = 0; - max = infinite; - break; - case '?': - min = 0; - max = 1; - break; - case '+': - min = 1; - max = infinite; - break; - case '{': - enforce(next(), "Unexpected end of regex pattern"); - enforce(std.ascii.isDigit(current), "First number required in repetition"); - min = parseDecimal(); - if(current == '}') - max = min; - else if(current == ',') - { - next(); - if(std.ascii.isDigit(current)) - max = parseDecimal(); - else if(current == '}') - max = infinite; - else - error("Unexpected symbol in regex pattern"); - skipSpace(); - if(current != '}') - error("Unmatched '{' in regex pattern"); - } - else - error("Unexpected symbol in regex pattern"); - if(min > max) - error("Illegal {n,m} quantifier"); - break; - default: - if(replace) - { - copy(ir[offset + 1 .. $], ir[offset .. $ - 1]); - ir.length -= 1; - } - return; - } - uint len = cast(uint)ir.length - offset - replace; - bool greedy = true; - //check only if we managed to get new symbol - if(next() && current == '?') - { - greedy = false; - next(); - } - if(max != infinite) - { - if(min != 1 || max != 1) - { - Bytecode op = Bytecode(greedy ? IR.RepeatStart : IR.RepeatQStart, len); - if(replace) - ir[offset] = op; - else - insertInPlace(ir, offset, op); - put(Bytecode(greedy ? IR.RepeatEnd : IR.RepeatQEnd, len)); - put(Bytecode.init); //hotspot - putRaw(1); - putRaw(min); - putRaw(max); - counterDepth = std.algorithm.max(counterDepth, nesting+1); - } - } - else if(min) //&& max is infinite - { - if(min != 1) - { - Bytecode op = Bytecode(greedy ? IR.RepeatStart : IR.RepeatQStart, len); - if(replace) - ir[offset] = op; - else - insertInPlace(ir, offset, op); - offset += 1;//so it still points to the repeated block - put(Bytecode(greedy ? IR.RepeatEnd : IR.RepeatQEnd, len)); - put(Bytecode.init); //hotspot - putRaw(1); - putRaw(min); - putRaw(min); - counterDepth = std.algorithm.max(counterDepth, nesting+1); - } - else if(replace) - { - copy(ir[offset+1 .. $], ir[offset .. $-1]); - ir.length -= 1; - } - put(Bytecode(greedy ? IR.InfiniteStart : IR.InfiniteQStart, len)); - enforce(ir.length + len < maxCompiledLength, "maximum compiled pattern length is exceeded"); - ir ~= ir[offset .. offset+len]; - //IR.InfinteX is always a hotspot - put(Bytecode(greedy ? IR.InfiniteEnd : IR.InfiniteQEnd, len)); - put(Bytecode.init); //merge index - } - else//vanila {0,inf} - { - Bytecode op = Bytecode(greedy ? IR.InfiniteStart : IR.InfiniteQStart, len); - if(replace) - ir[offset] = op; - else - insertInPlace(ir, offset, op); - //IR.InfinteX is always a hotspot - put(Bytecode(greedy ? IR.InfiniteEnd : IR.InfiniteQEnd, len)); - put(Bytecode.init); //merge index - - } - } - - //parse and store IR for atom - void parseAtom() - { - if(empty) - return; - switch(current) - { - case '*', '?', '+', '|', '{', '}': - error("'*', '+', '?', '{', '}' not allowed in atom"); - break; - case '.': - put(Bytecode(IR.Any, 0)); - next(); - break; - case '[': - parseCharset(); - break; - case '\\': - enforce(_next(), "Unfinished escape sequence"); - parseEscape(); - break; - case '^': - put(Bytecode(IR.Bol, 0)); - next(); - break; - case '$': - put(Bytecode(IR.Eol, 0)); - next(); - break; - default: - //FIXME: getCommonCasing in new std uni - if(re_flags & RegexOption.casefold) - { - auto range = simpleCaseFoldings(current); - assert(range.length <= 5); - if(range.length == 1) - put(Bytecode(IR.Char, range.front)); - else - foreach(v; range) - put(Bytecode(IR.OrChar, v, cast(uint)range.length)); - } - else - put(Bytecode(IR.Char, current)); - next(); - } - } - - //generate code for start of lookaround: (?= (?! (?<= (?= 0) - { - if(ivals.length*2 > maxCharsetUsed) - put(Bytecode(IR.Trie, cast(uint)n)); - else - put(Bytecode(IR.CodepointSet, cast(uint)n)); - return; - } - if(ivals.length*2 > maxCharsetUsed) - { - auto t = getTrie(set); - put(Bytecode(IR.Trie, cast(uint)tries.length)); - tries ~= t; - debug(std_regex_allocation) writeln("Trie generated"); - } - else - { - put(Bytecode(IR.CodepointSet, cast(uint)charsets.length)); - tries ~= Trie.init; - } - charsets ~= set; - assert(charsets.length == tries.length); - } - } - - //parse and generate IR for escape stand alone escape sequence - @trusted void parseEscape() - {//accesses array of appender - - switch(current) - { - case 'f': next(); put(Bytecode(IR.Char, '\f')); break; - case 'n': next(); put(Bytecode(IR.Char, '\n')); break; - case 'r': next(); put(Bytecode(IR.Char, '\r')); break; - case 't': next(); put(Bytecode(IR.Char, '\t')); break; - case 'v': next(); put(Bytecode(IR.Char, '\v')); break; - - case 'd': - next(); - charsetToIr(unicode.Nd); - break; - case 'D': - next(); - charsetToIr(unicode.Nd.inverted); - break; - case 'b': next(); put(Bytecode(IR.Wordboundary, 0)); break; - case 'B': next(); put(Bytecode(IR.Notwordboundary, 0)); break; - case 's': - next(); - charsetToIr(unicode.White_Space); - break; - case 'S': - next(); - charsetToIr(unicode.White_Space.inverted); - break; - case 'w': - next(); - charsetToIr(wordCharacter); - break; - case 'W': - next(); - charsetToIr(wordCharacter.inverted); - break; - case 'p': case 'P': - auto CodepointSet = parseUnicodePropertySpec(current == 'P'); - charsetToIr(CodepointSet); - break; - case 'x': - uint code = parseUniHex(pat, 2); - next(); - put(Bytecode(IR.Char,code)); - break; - case 'u': case 'U': - uint code = parseUniHex(pat, current == 'u' ? 4 : 8); - next(); - put(Bytecode(IR.Char, code)); - break; - case 'c': //control codes - Bytecode code = Bytecode(IR.Char, parseControlCode()); - next(); - put(code); - break; - case '0': - next(); - put(Bytecode(IR.Char, 0));//NUL character - break; - case '1': .. case '9': - uint nref = cast(uint)current - '0'; - uint maxBackref = sum(groupStack.data); - enforce(nref < maxBackref, "Backref to unseen group"); - //perl's disambiguation rule i.e. - //get next digit only if there is such group number - while(nref < maxBackref && next() && std.ascii.isDigit(current)) - { - nref = nref * 10 + current - '0'; - } - if(nref >= maxBackref) - nref /= 10; - enforce(!isOpenGroup(nref), "Backref to open group"); - uint localLimit = maxBackref - groupStack.top; - if(nref >= localLimit) - { - put(Bytecode(IR.Backref, nref-localLimit)); - ir[$-1].setLocalRef(); - } - else - put(Bytecode(IR.Backref, nref)); - markBackref(nref); - break; - default: - auto op = Bytecode(IR.Char, current); - next(); - put(op); - } - } - - //parse and return a CodepointSet for \p{...Property...} and \P{...Property..}, - //\ - assumed to be processed, p - is current - CodepointSet parseUnicodePropertySpec(bool negated) - { - enum MAX_PROPERTY = 128; - char[MAX_PROPERTY] result; - uint k = 0; - enforce(next()); - if(current == '{') - { - while(k < MAX_PROPERTY && next() && current !='}' && current !=':') - if(current != '-' && current != ' ' && current != '_') - result[k++] = cast(char)std.ascii.toLower(current); - enforce(k != MAX_PROPERTY, "invalid property name"); - enforce(current == '}', "} expected "); - } - else - {//single char properties e.g.: \pL, \pN ... - enforce(current < 0x80, "invalid property name"); - result[k++] = cast(char)current; - } - auto s = getUnicodeSet(result[0..k], negated, - cast(bool)(re_flags & RegexOption.casefold)); - enforce(!s.empty, "unrecognized unicode property spec"); - next(); - return s; - } - - // - @trusted void error(string msg) - { - auto app = appender!string(); - ir = null; - formattedWrite(app, "%s\nPattern with error: `%s` <--HERE-- `%s`", - msg, origin[0..$-pat.length], pat); - throw new RegexException(app.data); - } - - alias Char = BasicElementOf!R; - //packages parsing results into a RegEx object - @property Regex!Char program() - { - return Regex!Char(this); - } -} - - -/++ - $(D Regex) object holds regular expression pattern in compiled form. - Instances of this object are constructed via calls to $(D regex). - This is an intended form for caching and storage of frequently - used regular expressions. -+/ -public struct Regex(Char) -{ - //temporary workaround for identifier lookup - CodepointSet[] charsets; // - Bytecode[] ir; //compiled bytecode of pattern - - /++ - Test if this object doesn't contain any compiled pattern. - Example: - --- - Regex!char r; - assert(r.empty); - r = regex(""); // Note: "" is a valid regex pattern. - assert(!r.empty); - --- - +/ - @safe @property bool empty() const nothrow { return ir is null; } - - /++ - A range of all the named captures in the regex. - Example: - ---- - import std.range; - import std.algorithm; - - auto re = regex(`(?P\w+) = (?P\d+)`); - auto nc = re.namedCaptures; - static assert(isRandomAccessRange!(typeof(nc))); - assert(!nc.empty); - assert(nc.length == 2); - assert(nc.equal(["name", "var"])); - assert(nc[0] == "name"); - assert(nc[1..$].equal(["var"])); - ---- - +/ - @safe @property auto namedCaptures() - { - static struct NamedGroupRange - { - private: - NamedGroup[] groups; - size_t start; - size_t end; - public: - this(NamedGroup[] g, size_t s, size_t e) - { - assert(s <= e); - assert(e <= g.length); - groups = g; - start = s; - end = e; - } - - @property string front() { return groups[start].name; } - @property string back() { return groups[end-1].name; } - @property bool empty() { return start >= end; } - @property size_t length() { return end - start; } - alias opDollar = length; - @property NamedGroupRange save() - { - return NamedGroupRange(groups, start, end); - } - void popFront() { assert(!empty); start++; } - void popBack() { assert(!empty); end--; } - string opIndex()(size_t i) - { - assert(start + i < end, - "Requested named group is out of range."); - return groups[start+i].name; - } - NamedGroupRange opSlice(size_t low, size_t high) { - assert(low <= high); - assert(start + high <= end); - return NamedGroupRange(groups, start + low, start + high); - } - NamedGroupRange opSlice() { return this.save; } - } - return NamedGroupRange(dict, 0, dict.length); - } - -private: - NamedGroup[] dict; //maps name -> user group number - uint ngroup; //number of internal groups - uint maxCounterDepth; //max depth of nested {n,m} repetitions - uint hotspotTableSize; //number of entries in merge table - uint threadCount; - uint flags; //global regex flags - public const(Trie)[] tries; // - uint[] backrefed; //bit array of backreferenced submatches - Kickstart!Char kickstart; - - //bit access helper - uint isBackref(uint n) - { - if(n/32 >= backrefed.length) - return 0; - return backrefed[n / 32] & (1 << (n & 31)); - } - - //check if searching is not needed - void checkIfOneShot() - { - if(flags & RegexOption.multiline) - return; - L_CheckLoop: - for(uint i = 0; i < ir.length; i += ir[i].length) - { - switch(ir[i].code) - { - case IR.Bol: - flags |= RegexInfo.oneShot; - break L_CheckLoop; - case IR.GroupStart, IR.GroupEnd, IR.Eol, IR.Wordboundary, IR.Notwordboundary: - break; - default: - break L_CheckLoop; - } - } - } - - /+ - lightweight post process step, - only essentials - +/ - @trusted void lightPostprocess() - {//@@@BUG@@@ write is @system - struct FixedStack(T) - { - T[] arr; - uint _top; - //this(T[] storage){ arr = storage; _top = -1; } - @property ref T top(){ assert(!empty); return arr[_top]; } - void push(T x){ arr[++_top] = x; } - T pop() { assert(!empty); return arr[_top--]; } - @property bool empty(){ return _top == -1; } - } - auto counterRange = FixedStack!uint(new uint[maxCounterDepth+1], -1); - counterRange.push(1); - ulong cumRange = 0; - for(uint i = 0; i < ir.length; i += ir[i].length) - { - if(ir[i].hotspot) - { - assert(i + 1 < ir.length, - "unexpected end of IR while looking for hotspot"); - ir[i+1] = Bytecode.fromRaw(hotspotTableSize); - hotspotTableSize += counterRange.top; - } - switch(ir[i].code) - { - case IR.RepeatStart, IR.RepeatQStart: - uint repEnd = cast(uint)(i + ir[i].data + IRL!(IR.RepeatStart)); - assert(ir[repEnd].code == ir[i].paired.code); - uint max = ir[repEnd + 4].raw; - ir[repEnd+2].raw = counterRange.top; - ir[repEnd+3].raw *= counterRange.top; - ir[repEnd+4].raw *= counterRange.top; - ulong cntRange = cast(ulong)(max)*counterRange.top; - cumRange += cntRange; - enforce(cumRange < maxCumulativeRepetitionLength, - "repetition length limit is exceeded"); - counterRange.push(cast(uint)cntRange + counterRange.top); - threadCount += counterRange.top; - break; - case IR.RepeatEnd, IR.RepeatQEnd: - threadCount += counterRange.top; - counterRange.pop(); - break; - case IR.GroupStart: - if(isBackref(ir[i].data)) - ir[i].setBackrefence(); - threadCount += counterRange.top; - break; - case IR.GroupEnd: - if(isBackref(ir[i].data)) - ir[i].setBackrefence(); - threadCount += counterRange.top; - break; - default: - threadCount += counterRange.top; - } - } - checkIfOneShot(); - if(!(flags & RegexInfo.oneShot)) - kickstart = Kickstart!Char(this, new uint[](256)); - debug(std_regex_allocation) writefln("IR processed, max threads: %d", threadCount); - } - - //IR code validator - proper nesting, illegal instructions, etc. - @trusted void validate() - {//@@@BUG@@@ text is @system - for(uint pc = 0; pc < ir.length; pc += ir[pc].length) - { - if(ir[pc].isStart || ir[pc].isEnd) - { - uint dest = ir[pc].indexOfPair(pc); - assert(dest < ir.length, text("Wrong length in opcode at pc=", - pc, " ", dest, " vs ", ir.length)); - assert(ir[dest].paired == ir[pc], - text("Wrong pairing of opcodes at pc=", pc, "and pc=", dest)); - } - else if(ir[pc].isAtom) - { - - } - else - assert(0, text("Unknown type of instruction at pc=", pc)); - } - } - - //print out disassembly a program's IR - @trusted debug(std_regex_parser) void print() const - {//@@@BUG@@@ write is system - for(uint i = 0; i < ir.length; i += ir[i].length) - { - writefln("%d\t%s ", i, disassemble(ir, i, dict)); - } - writeln("Total merge table size: ", hotspotTableSize); - writeln("Max counter nesting depth: ", maxCounterDepth); - } - - // - this(S)(Parser!(S) p) - { - ir = p.ir; - dict = p.dict; - ngroup = p.groupStack.top; - maxCounterDepth = p.counterDepth; - flags = p.re_flags; - charsets = p.charsets; - tries = p.tries; - backrefed = p.backrefed; - lightPostprocess(); - debug(std_regex_parser) - { - print(); - } - version(assert) validate(); - } -} - -unittest -{ - auto re = regex(`(?P\w+) = (?P\d+)`); - auto nc = re.namedCaptures; - static assert(isRandomAccessRange!(typeof(nc))); - assert(!nc.empty); - assert(nc.length == 2); - assert(nc.equal(["name", "var"])); - assert(nc[0] == "name"); - assert(nc[1..$].equal(["var"])); - - re = regex(`(\w+) (?P\w+) (\w+)`); - nc = re.namedCaptures; - assert(nc.length == 1); - assert(nc[0] == "named"); - assert(nc.front == "named"); - assert(nc.back == "named"); - - re = regex(`(\w+) (\w+)`); - nc = re.namedCaptures; - assert(nc.empty); - - re = regex(`(?P\d{4})/(?P\d{2})/(?P\d{2})/`); - nc = re.namedCaptures; - auto cp = nc.save; - assert(nc.equal(cp)); - nc.popFront(); - assert(nc.equal(cp[1..$])); - nc.popBack(); - assert(nc.equal(cp[1 .. $ - 1])); -} - -// -@trusted uint lookupNamedGroup(String)(NamedGroup[] dict, String name) -{//equal is @system? - auto fnd = assumeSorted!"cmp(a,b) < 0"(map!"a.name"(dict)).lowerBound(name).length; - enforce(fnd < dict.length && equal(dict[fnd].name, name), - text("no submatch named ", name)); - return dict[fnd].group; -} - -//whether ch is one of unicode newline sequences -//0-arg template due to @@@BUG@@@ 10985 -bool endOfLine()(dchar front, bool seenCr) -{ - return ((front == '\n') ^ seenCr) || front == '\r' - || front == NEL || front == LS || front == PS; -} - -// -//0-arg template due to @@@BUG@@@ 10985 -bool startOfLine()(dchar back, bool seenNl) -{ - return ((back == '\r') ^ seenNl) || back == '\n' - || back == NEL || back == LS || back == PS; -} - -//Test if bytecode starting at pc in program 're' can match given codepoint -//Returns: 0 - can't tell, -1 if doesn't match -int quickTestFwd(RegEx)(uint pc, dchar front, const ref RegEx re) -{ - static assert(IRL!(IR.OrChar) == 1);//used in code processing IR.OrChar - for(;;) - switch(re.ir[pc].code) - { - case IR.OrChar: - uint len = re.ir[pc].sequence; - uint end = pc + len; - if(re.ir[pc].data != front && re.ir[pc+1].data != front) - { - for(pc = pc+2; pc < end; pc++) - if(re.ir[pc].data == front) - break; - if(pc == end) - return -1; - } - return 0; - case IR.Char: - if(front == re.ir[pc].data) - return 0; - else - return -1; - case IR.Any: - return 0; - case IR.CodepointSet: - if(re.charsets[re.ir[pc].data].scanFor(front)) - return 0; - else - return -1; - case IR.GroupStart, IR.GroupEnd: - pc += IRL!(IR.GroupStart); - break; - case IR.Trie: - if(re.tries[re.ir[pc].data][front]) - return 0; - else - return -1; - default: - return 0; - } -} - -/* - Useful utility for self-testing, an infinite range of string samples - that _have_ to match given compiled regex. - Caveats: supports only a simple subset of bytecode. -*/ -@trusted public struct SampleGenerator(Char) -{ - import std.random; - Regex!Char re; - Appender!(char[]) app; - uint limit, seed; - Xorshift gen; - //generator for pattern r, with soft maximum of threshold elements - //and a given random seed - this(ref Regex!Char r, uint threshold, uint randomSeed) - { - re = r; - limit = threshold; - seed = randomSeed; - app = appender!(Char[])(); - compose(); - } - - uint rand(uint x) - { - uint r = gen.front % x; - gen.popFront(); - return r; - } - - void compose() - { - uint pc = 0, counter = 0, dataLenOld = uint.max; - for(;;) - { - switch(re.ir[pc].code) - { - case IR.Char: - formattedWrite(app,"%s", cast(dchar)re.ir[pc].data); - pc += IRL!(IR.Char); - break; - case IR.OrChar: - uint len = re.ir[pc].sequence; - formattedWrite(app, "%s", cast(dchar)re.ir[pc + rand(len)].data); - pc += len; - break; - case IR.CodepointSet: - case IR.Trie: - auto set = re.charsets[re.ir[pc].data]; - auto x = rand(set.ivals.length/2); - auto y = rand(set.ivals[x*2+1] - set.ivals[2*x]); - formattedWrite(app, "%s", cast(dchar)(set.ivals[2*x]+y)); - pc += IRL!(IR.CodepointSet); - break; - case IR.Any: - uint x; - do - { - x = rand(0x11_000); - }while(x == '\r' || x == '\n' || !isValidDchar(x)); - formattedWrite(app, "%s", cast(dchar)x); - pc += IRL!(IR.Any); - break; - case IR.GotoEndOr: - pc += IRL!(IR.GotoEndOr)+re.ir[pc].data; - assert(re.ir[pc].code == IR.OrEnd); - goto case; - case IR.OrEnd: - pc += IRL!(IR.OrEnd); - break; - case IR.OrStart: - pc += IRL!(IR.OrStart); - goto case; - case IR.Option: - uint next = pc + re.ir[pc].data + IRL!(IR.Option); - uint nOpt = 0; - //queue next Option - while(re.ir[next].code == IR.Option) - { - nOpt++; - next += re.ir[next].data + IRL!(IR.Option); - } - nOpt++; - nOpt = rand(nOpt); - for(;nOpt; nOpt--) - { - pc += re.ir[pc].data + IRL!(IR.Option); - } - assert(re.ir[pc].code == IR.Option); - pc += IRL!(IR.Option); - break; - case IR.RepeatStart:case IR.RepeatQStart: - pc += IRL!(IR.RepeatStart)+re.ir[pc].data; - goto case IR.RepeatEnd; - case IR.RepeatEnd: - case IR.RepeatQEnd: - uint len = re.ir[pc].data; - uint step = re.ir[pc+2].raw; - uint min = re.ir[pc+3].raw; - if(counter < min) - { - counter += step; - pc -= len; - break; - } - uint max = re.ir[pc+4].raw; - if(counter < max) - { - if(app.data.length < limit && rand(3) > 0) - { - pc -= len; - counter += step; - } - else - { - counter = counter%step; - pc += IRL!(IR.RepeatEnd); - } - } - else - { - counter = counter%step; - pc += IRL!(IR.RepeatEnd); - } - break; - case IR.InfiniteStart, IR.InfiniteQStart: - pc += re.ir[pc].data + IRL!(IR.InfiniteStart); - goto case IR.InfiniteEnd; //both Q and non-Q - case IR.InfiniteEnd: - case IR.InfiniteQEnd: - uint len = re.ir[pc].data; - if(app.data.length == dataLenOld) - { - pc += IRL!(IR.InfiniteEnd); - break; - } - dataLenOld = app.data.length; - if(app.data.length < limit && rand(3) > 0) - pc = pc - len; - else - pc = pc + IRL!(IR.InfiniteEnd); - break; - case IR.GroupStart, IR.GroupEnd: - pc += IRL!(IR.GroupStart); - break; - case IR.Bol, IR.Wordboundary, IR.Notwordboundary: - case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: - default: - return; - } - } - } - - @property Char[] front() - { - return app.data; - } - - @property empty(){ return false; } - - void popFront() - { - app.shrinkTo(0); - compose(); - } -} - -/++ - A $(D StaticRegex) is $(D Regex) object that contains specially - generated machine code to speed up matching. - Implicitly convertible to normal $(D Regex), - however doing so will result in losing this additional capability. -+/ -public struct StaticRegex(Char) -{ -private: - alias Matcher = BacktrackingMatcher!(true); - alias MatchFn = bool function(ref Matcher!Char) @trusted; - MatchFn nativeFn; -public: - Regex!Char _regex; - alias _regex this; - this(Regex!Char re, MatchFn fn) - { - _regex = re; - nativeFn = fn; - - } - -} - -//utility for shiftOr, returns a minimum number of bytes to test in a Char -uint effectiveSize(Char)() -{ - static if(is(Char == char)) - return 1; - else static if(is(Char == wchar)) - return 2; - else static if(is(Char == dchar)) - return 3; - else - static assert(0); -} - -/* - Kickstart engine using ShiftOr algorithm, - a bit parallel technique for inexact string searching. -*/ -struct ShiftOr(Char) -{ -private: - uint[] table; - uint fChar; - uint n_length; - enum charSize = effectiveSize!Char(); - //maximum number of chars in CodepointSet to process - enum uint charsetThreshold = 32_000; - static struct ShiftThread - { - uint[] tab; - uint mask; - uint idx; - uint pc, counter, hops; - this(uint newPc, uint newCounter, uint[] table) - { - pc = newPc; - counter = newCounter; - mask = 1; - idx = 0; - hops = 0; - tab = table; - } - - void setMask(uint idx, uint mask) - { - tab[idx] |= mask; - } - - void setInvMask(uint idx, uint mask) - { - tab[idx] &= ~mask; - } - - void set(alias setBits = setInvMask)(dchar ch) - { - static if(charSize == 3) - { - uint val = ch, tmask = mask; - setBits(val&0xFF, tmask); - tmask <<= 1; - val >>= 8; - setBits(val&0xFF, tmask); - tmask <<= 1; - val >>= 8; - assert(val <= 0x10); - setBits(val, tmask); - tmask <<= 1; - } - else - { - Char[dchar.sizeof/Char.sizeof] buf; - uint tmask = mask; - size_t total = encode(buf, ch); - for(size_t i = 0; i < total; i++, tmask<<=1) - { - static if(charSize == 1) - setBits(buf[i], tmask); - else static if(charSize == 2) - { - setBits(buf[i]&0xFF, tmask); - tmask <<= 1; - setBits(buf[i]>>8, tmask); - } - } - } - } - void add(dchar ch){ return set!setInvMask(ch); } - void advance(uint s) - { - mask <<= s; - idx += s; - } - @property bool full(){ return !mask; } - } - - static ShiftThread fork(ShiftThread t, uint newPc, uint newCounter) - { - ShiftThread nt = t; - nt.pc = newPc; - nt.counter = newCounter; - return nt; - } - - @trusted static ShiftThread fetch(ref ShiftThread[] worklist) - { - auto t = worklist[$-1]; - worklist.length -= 1; - if(!__ctfe) - cast(void)worklist.assumeSafeAppend(); - return t; - } - - static uint charLen(uint ch) - { - assert(ch <= 0x10FFFF); - return codeLength!Char(cast(dchar)ch)*charSize; - } - -public: - @trusted this(ref Regex!Char re, uint[] memory) - { - assert(memory.length == 256); - fChar = uint.max; - L_FindChar: - for(size_t i = 0;;) - { - switch(re.ir[i].code) - { - case IR.Char: - fChar = re.ir[i].data; - static if(charSize != 3) - { - Char[dchar.sizeof/Char.sizeof] buf; - encode(buf, fChar); - fChar = buf[0]; - } - fChar = fChar & 0xFF; - break L_FindChar; - case IR.GroupStart, IR.GroupEnd: - i += IRL!(IR.GroupStart); - break; - case IR.Bol, IR.Wordboundary, IR.Notwordboundary: - i += IRL!(IR.Bol); - break; - default: - break L_FindChar; - } - } - table = memory; - table[] = uint.max; - ShiftThread[] trs; - ShiftThread t = ShiftThread(0, 0, table); - //locate first fixed char if any - n_length = 32; - for(;;) - { - L_Eval_Thread: - for(;;) - { - switch(re.ir[t.pc].code) - { - case IR.Char: - uint s = charLen(re.ir[t.pc].data); - if(t.idx+s > n_length) - goto L_StopThread; - t.add(re.ir[t.pc].data); - t.advance(s); - t.pc += IRL!(IR.Char); - break; - case IR.OrChar://assumes IRL!(OrChar) == 1 - uint len = re.ir[t.pc].sequence; - uint end = t.pc + len; - uint[Bytecode.maxSequence] s; - uint numS; - for(uint i = 0; i < len; i++) - { - auto x = charLen(re.ir[t.pc+i].data); - if(countUntil(s[0..numS], x) < 0) - s[numS++] = x; - } - for(uint i = t.pc; i < end; i++) - { - t.add(re.ir[i].data); - } - for(uint i = 0; i < numS; i++) - { - auto tx = fork(t, t.pc + len, t.counter); - if(tx.idx + s[i] <= n_length) - { - tx.advance(s[i]); - trs ~= tx; - } - } - if(!trs.empty) - t = fetch(trs); - else - goto L_StopThread; - break; - case IR.CodepointSet: - case IR.Trie: - auto set = re.charsets[re.ir[t.pc].data]; - uint[4] s; - uint numS; - static if(charSize == 3) - { - s[0] = charSize; - numS = 1; - } - else - { - - static if(charSize == 1) - static immutable codeBounds = [0x0, 0x7F, 0x80, 0x7FF, 0x800, 0xFFFF, 0x10000, 0x10FFFF]; - else //== 2 - static immutable codeBounds = [0x0, 0xFFFF, 0x10000, 0x10FFFF]; - uint[] arr = new uint[set.byInterval.length * 2]; - size_t ofs = 0; - foreach(ival; set.byInterval) - { - arr[ofs++] = ival.a; - arr[ofs++] = ival.b; - } - auto srange = assumeSorted!"a <= b"(arr); - for(uint i = 0; i < codeBounds.length/2; i++) - { - auto start = srange.lowerBound(codeBounds[2*i]).length; - auto end = srange.lowerBound(codeBounds[2*i+1]).length; - if(end > start || (end == start && (end & 1))) - s[numS++] = (i+1)*charSize; - } - } - if(numS == 0 || t.idx + s[numS-1] > n_length) - goto L_StopThread; - auto chars = set.length; - if(chars > charsetThreshold) - goto L_StopThread; - foreach(ch; set.byCodepoint) - { - //avoid surrogate pairs - if(0xD800 <= ch && ch <= 0xDFFF) - continue; - t.add(ch); - } - for(uint i = 0; i < numS; i++) - { - auto tx = fork(t, t.pc + IRL!(IR.CodepointSet), t.counter); - tx.advance(s[i]); - trs ~= tx; - } - if(!trs.empty) - t = fetch(trs); - else - goto L_StopThread; - break; - case IR.Any: - goto L_StopThread; - - case IR.GotoEndOr: - t.pc += IRL!(IR.GotoEndOr)+re.ir[t.pc].data; - assert(re.ir[t.pc].code == IR.OrEnd); - goto case; - case IR.OrEnd: - t.pc += IRL!(IR.OrEnd); - break; - case IR.OrStart: - t.pc += IRL!(IR.OrStart); - goto case; - case IR.Option: - uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option); - //queue next Option - if(re.ir[next].code == IR.Option) - { - trs ~= fork(t, next, t.counter); - } - t.pc += IRL!(IR.Option); - break; - case IR.RepeatStart:case IR.RepeatQStart: - t.pc += IRL!(IR.RepeatStart)+re.ir[t.pc].data; - goto case IR.RepeatEnd; - case IR.RepeatEnd: - case IR.RepeatQEnd: - uint len = re.ir[t.pc].data; - uint step = re.ir[t.pc+2].raw; - uint min = re.ir[t.pc+3].raw; - if(t.counter < min) - { - t.counter += step; - t.pc -= len; - break; - } - uint max = re.ir[t.pc+4].raw; - if(t.counter < max) - { - trs ~= fork(t, t.pc - len, t.counter + step); - t.counter = t.counter%step; - t.pc += IRL!(IR.RepeatEnd); - } - else - { - t.counter = t.counter%step; - t.pc += IRL!(IR.RepeatEnd); - } - break; - case IR.InfiniteStart, IR.InfiniteQStart: - t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart); - goto case IR.InfiniteEnd; //both Q and non-Q - case IR.InfiniteEnd: - case IR.InfiniteQEnd: - uint len = re.ir[t.pc].data; - uint pc1, pc2; //branches to take in priority order - if(++t.hops == 32) - goto L_StopThread; - pc1 = t.pc + IRL!(IR.InfiniteEnd); - pc2 = t.pc - len; - trs ~= fork(t, pc2, t.counter); - t.pc = pc1; - break; - case IR.GroupStart, IR.GroupEnd: - t.pc += IRL!(IR.GroupStart); - break; - case IR.Bol, IR.Wordboundary, IR.Notwordboundary: - t.pc += IRL!(IR.Bol); - break; - case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: - t.pc += IRL!(IR.LookaheadStart) + IRL!(IR.LookaheadEnd) + re.ir[t.pc].data; - break; - default: - L_StopThread: - assert(re.ir[t.pc].code >= 0x80, text(re.ir[t.pc].code)); - debug (fred_search) writeln("ShiftOr stumbled on ",re.ir[t.pc].mnemonic); - n_length = min(t.idx, n_length); - break L_Eval_Thread; - } - } - if(trs.empty) - break; - t = fetch(trs); - } - debug(std_regex_search) - { - writeln("Min length: ", n_length); - } - } - - @property bool empty() const { return n_length == 0; } - - @property uint length() const{ return n_length/charSize; } - - // lookup compatible bit pattern in haystack, return starting index - // has a useful trait: if supplied with valid UTF indexes, - // returns only valid UTF indexes - // (that given the haystack in question is valid UTF string) - @trusted size_t search(const(Char)[] haystack, size_t idx) - {//@BUG: apparently assumes little endian machines - assert(!empty); - auto p = cast(const(ubyte)*)(haystack.ptr+idx); - uint state = uint.max; - uint limit = 1u<<(n_length - 1u); - debug(std_regex_search) writefln("Limit: %32b",limit); - if(fChar != uint.max) - { - const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length); - const orginalAlign = cast(size_t)p & (Char.sizeof-1); - while(p != end) - { - if(!~state) - {//speed up seeking first matching place - for(;;) - { - assert(p <= end, text(p," vs ", end)); - p = cast(ubyte*)memchr(p, fChar, end - p); - if(!p) - return haystack.length; - if((cast(size_t)p & (Char.sizeof-1)) == orginalAlign) - break; - if(++p == end) - return haystack.length; - } - state = ~1u; - assert((cast(size_t)p & (Char.sizeof-1)) == orginalAlign); - static if(charSize == 3) - { - state = (state<<1) | table[p[1]]; - state = (state<<1) | table[p[2]]; - p += 4; - } - else - p++; - //first char is tested, see if that's all - if(!(state & limit)) - return (p-cast(ubyte*)haystack.ptr)/Char.sizeof - -length; - } - else - {//have some bits/states for possible matches, - //use the usual shift-or cycle - static if(charSize == 3) - { - state = (state<<1) | table[p[0]]; - state = (state<<1) | table[p[1]]; - state = (state<<1) | table[p[2]]; - p += 4; - } - else - { - state = (state<<1) | table[p[0]]; - p++; - } - if(!(state & limit)) - return (p-cast(ubyte*)haystack.ptr)/Char.sizeof - -length; - } - debug(std_regex_search) writefln("State: %32b", state); - } - } - else - { - //normal path, partially unrolled for char/wchar - static if(charSize == 3) - { - const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length); - while(p != end) - { - state = (state<<1) | table[p[0]]; - state = (state<<1) | table[p[1]]; - state = (state<<1) | table[p[2]]; - p += 4; - if(!(state & limit))//division rounds down for dchar - return (p-cast(ubyte*)haystack.ptr)/Char.sizeof - -length; - } - } - else - { - auto len = cast(ubyte*)(haystack.ptr + haystack.length) - p; - size_t i = 0; - if(len & 1) - { - state = (state<<1) | table[p[i++]]; - if(!(state & limit)) - return idx+i/Char.sizeof-length; - } - while(i < len) - { - state = (state<<1) | table[p[i++]]; - if(!(state & limit)) - return idx+i/Char.sizeof - -length; - state = (state<<1) | table[p[i++]]; - if(!(state & limit)) - return idx+i/Char.sizeof - -length; - debug(std_regex_search) writefln("State: %32b", state); - } - } - } - return haystack.length; - } - - @system debug static void dump(uint[] table) - {//@@@BUG@@@ writef(ln) is @system - import std.stdio; - for(size_t i = 0; i < table.length; i += 4) - { - writefln("%32b %32b %32b %32b",table[i], table[i+1], table[i+2], table[i+3]); - } - } -} - -unittest -{ - - @trusted void test_fixed(alias Kick)() - { - foreach(i, v; TypeTuple!(char, wchar, dchar)) - { - alias Char = v; - alias String = immutable(v)[]; - auto r = regex(to!String(`abc$`)); - auto kick = Kick!Char(r, new uint[256]); - assert(kick.length == 3, text(Kick.stringof," ",v.stringof, " == ", kick.length)); - auto r2 = regex(to!String(`(abc){2}a+`)); - kick = Kick!Char(r2, new uint[256]); - assert(kick.length == 7, text(Kick.stringof,v.stringof," == ", kick.length)); - auto r3 = regex(to!String(`\b(a{2}b{3}){2,4}`)); - kick = Kick!Char(r3, new uint[256]); - assert(kick.length == 10, text(Kick.stringof,v.stringof," == ", kick.length)); - auto r4 = regex(to!String(`\ba{2}c\bxyz`)); - kick = Kick!Char(r4, new uint[256]); - assert(kick.length == 6, text(Kick.stringof,v.stringof, " == ", kick.length)); - auto r5 = regex(to!String(`\ba{2}c\b`)); - kick = Kick!Char(r5, new uint[256]); - size_t x = kick.search("aabaacaa", 0); - assert(x == 3, text(Kick.stringof,v.stringof," == ", kick.length)); - x = kick.search("aabaacaa", x+1); - assert(x == 8, text(Kick.stringof,v.stringof," == ", kick.length)); - } - } - @trusted void test_flex(alias Kick)() - { - foreach(i, v;TypeTuple!(char, wchar, dchar)) - { - alias Char = v; - alias String = immutable(v)[]; - auto r = regex(to!String(`abc[a-z]`)); - auto kick = Kick!Char(r, new uint[256]); - auto x = kick.search(to!String("abbabca"), 0); - assert(x == 3, text("real x is ", x, " ",v.stringof)); - - auto r2 = regex(to!String(`(ax|bd|cdy)`)); - String s2 = to!String("abdcdyabax"); - kick = Kick!Char(r2, new uint[256]); - x = kick.search(s2, 0); - assert(x == 1, text("real x is ", x)); - x = kick.search(s2, x+1); - assert(x == 3, text("real x is ", x)); - x = kick.search(s2, x+1); - assert(x == 8, text("real x is ", x)); - auto rdot = regex(to!String(`...`)); - kick = Kick!Char(rdot, new uint[256]); - assert(kick.length == 0); - auto rN = regex(to!String(`a(b+|c+)x`)); - kick = Kick!Char(rN, new uint[256]); - assert(kick.length == 3); - assert(kick.search("ababx",0) == 2); - assert(kick.search("abaacba",0) == 3);//expected inexact - - } - } - test_fixed!(ShiftOr)(); - test_flex!(ShiftOr)(); -} - -alias Kickstart = ShiftOr; - -//Simple UTF-string abstraction compatible with stream interface -struct Input(Char) - if(is(Char :dchar)) -{ - alias DataIndex = size_t; - enum { isLoopback = false }; - alias String = const(Char)[]; - String _origin; - size_t _index; - - //constructs Input object out of plain string - this(String input, size_t idx = 0) - { - _origin = input; - _index = idx; - } - - //codepoint at current stream position - bool nextChar(ref dchar res, ref size_t pos) - { - pos = _index; - if(_index == _origin.length) - return false; - res = std.utf.decode(_origin, _index); - return true; - } - @property bool atEnd(){ - return _index == _origin.length; - } - bool search(Kickstart)(ref Kickstart kick, ref dchar res, ref size_t pos) - { - size_t idx = kick.search(_origin, _index); - _index = idx; - return nextChar(res, pos); - } - - //index of at End position - @property size_t lastIndex(){ return _origin.length; } - - //support for backtracker engine, might not be present - void reset(size_t index){ _index = index; } - - String opSlice(size_t start, size_t end){ return _origin[start..end]; } - - struct BackLooper - { - alias DataIndex = size_t; - enum { isLoopback = true }; - String _origin; - size_t _index; - this(Input input, size_t index) - { - _origin = input._origin; - _index = index; - } - @trusted bool nextChar(ref dchar res,ref size_t pos) - { - pos = _index; - if(_index == 0) - return false; - - res = _origin[0.._index].back; - _index -= std.utf.strideBack(_origin, _index); - - return true; - } - @property atEnd(){ return _index == 0 || _index == std.utf.strideBack(_origin, _index); } - auto loopBack(size_t index){ return Input(_origin, index); } - - //support for backtracker engine, might not be present - //void reset(size_t index){ _index = index ? index-std.utf.strideBack(_origin, index) : 0; } - void reset(size_t index){ _index = index; } - - String opSlice(size_t start, size_t end){ return _origin[end..start]; } - //index of at End position - @property size_t lastIndex(){ return 0; } - } - auto loopBack(size_t index){ return BackLooper(this, index); } -} - -//both helperd below are internal, on its own are quite "explosive" - -//unsafe, no initialization of elements -@system T[] mallocArray(T)(size_t len) -{ - return (cast(T*)malloc(len * T.sizeof))[0 .. len]; -} - -//very unsafe, no initialization -@system T[] arrayInChunk(T)(size_t len, ref void[] chunk) -{ - auto ret = (cast(T*)chunk.ptr)[0..len]; - chunk = chunk[len * T.sizeof .. $]; - return ret; -} - -/+ - BacktrackingMatcher implements backtracking scheme of matching - regular expressions. -+/ -template BacktrackingMatcher(bool CTregex) -{ - @trusted struct BacktrackingMatcher(Char, Stream = Input!Char) - if(is(Char : dchar)) - { - alias DataIndex = Stream.DataIndex; - struct State - {//top bit in pc is set if saved along with matches - DataIndex index; - uint pc, counter, infiniteNesting; - } - static assert(State.sizeof % size_t.sizeof == 0); - enum stateSize = State.sizeof / size_t.sizeof; - enum initialStack = 1<<11; // items in a block of segmented stack - alias const(Char)[] String; - alias RegEx = Regex!Char; - alias MatchFn = bool function (ref BacktrackingMatcher!(Char, Stream)); - RegEx re; //regex program - static if(CTregex) - MatchFn nativeFn; //native code for that program - //Stream state - Stream s; - DataIndex index; - dchar front; - bool exhausted; - //backtracking machine state - uint pc, counter; - DataIndex lastState = 0; //top of state stack - DataIndex[] trackers; - static if(!CTregex) - uint infiniteNesting; - size_t[] memory; - //local slice of matches, global for backref - Group!DataIndex[] matches, backrefed; - - static if(__traits(hasMember,Stream, "search")) - { - enum kicked = true; - } - else - enum kicked = false; - - static size_t initialMemory(const ref RegEx re) - { - return (re.ngroup+1)*DataIndex.sizeof //trackers - + stackSize(re)*size_t.sizeof; - } - - static size_t stackSize(const ref RegEx re) - { - return initialStack*(stateSize + re.ngroup*(Group!DataIndex).sizeof/size_t.sizeof)+1; - } - - @property bool atStart(){ return index == 0; } - - @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } - - void next() - { - if(!s.nextChar(front, index)) - index = s.lastIndex; - } - - void search() - { - static if(kicked) - { - if(!s.search(re.kickstart, front, index)) - { - index = s.lastIndex; - } - } - else - next(); - } - - // - void newStack() - { - auto chunk = mallocArray!(size_t)(stackSize(re)); - chunk[0] = cast(size_t)(memory.ptr); - memory = chunk[1..$]; - } - - void initExternalMemory(void[] memBlock) - { - trackers = arrayInChunk!(DataIndex)(re.ngroup+1, memBlock); - memory = cast(size_t[])memBlock; - memory[0] = 0; //hidden pointer - memory = memory[1..$]; - } - - void initialize(ref RegEx program, Stream stream, void[] memBlock) - { - re = program; - s = stream; - exhausted = false; - initExternalMemory(memBlock); - backrefed = null; - } - - auto dupTo(void[] memory) - { - typeof(this) tmp = this; - tmp.initExternalMemory(memory); - return tmp; - } - - this(ref RegEx program, Stream stream, void[] memBlock, dchar ch, DataIndex idx) - { - initialize(program, stream, memBlock); - front = ch; - index = idx; - } - - this(ref RegEx program, Stream stream, void[] memBlock) - { - initialize(program, stream, memBlock); - next(); - } - - auto fwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock) - { - alias BackMatcherTempl = .BacktrackingMatcher!(CTregex); - alias BackMatcher = BackMatcherTempl!(Char, Stream); - auto fwdMatcher = BackMatcher(matcher.re, s, memBlock, front, index); - return fwdMatcher; - } - - auto bwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock) - { - alias BackMatcherTempl = .BacktrackingMatcher!(CTregex); - alias BackMatcher = BackMatcherTempl!(Char, typeof(s.loopBack(index))); - auto fwdMatcher = - BackMatcher(matcher.re, s.loopBack(index), memBlock); - return fwdMatcher; - } - - // - bool matchFinalize() - { - size_t start = index; - if(matchImpl()) - {//stream is updated here - matches[0].begin = start; - matches[0].end = index; - if(!(re.flags & RegexOption.global) || atEnd) - exhausted = true; - if(start == index)//empty match advances input - next(); - return true; - } - else - return false; - } - - //lookup next match, fill matches with indices into input - bool match(Group!DataIndex[] matches) - { - debug(std_regex_matcher) - { - writeln("------------------------------------------"); - } - if(exhausted) //all matches collected - return false; - this.matches = matches; - if(re.flags & RegexInfo.oneShot) - { - exhausted = true; - DataIndex start = index; - auto m = matchImpl(); - if(m) - { - matches[0].begin = start; - matches[0].end = index; - } - return m; - } - static if(kicked) - { - if(!re.kickstart.empty) - { - for(;;) - { - - if(matchFinalize()) - return true; - else - { - if(atEnd) - break; - search(); - if(atEnd) - { - exhausted = true; - return matchFinalize(); - } - } - } - exhausted = true; - return false; //early return - } - } - //no search available - skip a char at a time - for(;;) - { - if(matchFinalize()) - return true; - else - { - if(atEnd) - break; - next(); - if(atEnd) - { - exhausted = true; - return matchFinalize(); - } - } - } - exhausted = true; - return false; - } - - /+ - match subexpression against input, - results are stored in matches - +/ - bool matchImpl() - { - static if(CTregex && is(typeof(nativeFn(this)))) - { - debug(std_regex_ctr) writeln("using C-T matcher"); - return nativeFn(this); - } - else - { - pc = 0; - counter = 0; - lastState = 0; - infiniteNesting = -1;//intentional - auto start = s._index; - debug(std_regex_matcher) - writeln("Try match starting at ", s[index..s.lastIndex]); - for(;;) - { - debug(std_regex_matcher) - writefln("PC: %s\tCNT: %s\t%s \tfront: %s src: %s", - pc, counter, disassemble(re.ir, pc, re.dict), - front, s._index); - switch(re.ir[pc].code) - { - case IR.OrChar://assumes IRL!(OrChar) == 1 - if(atEnd) - goto L_backtrack; - uint len = re.ir[pc].sequence; - uint end = pc + len; - if(re.ir[pc].data != front && re.ir[pc+1].data != front) - { - for(pc = pc+2; pc < end; pc++) - if(re.ir[pc].data == front) - break; - if(pc == end) - goto L_backtrack; - } - pc = end; - next(); - break; - case IR.Char: - if(atEnd || front != re.ir[pc].data) - goto L_backtrack; - pc += IRL!(IR.Char); - next(); - break; - case IR.Any: - if(atEnd || (!(re.flags & RegexOption.singleline) - && (front == '\r' || front == '\n'))) - goto L_backtrack; - pc += IRL!(IR.Any); - next(); - break; - case IR.CodepointSet: - if(atEnd || !re.charsets[re.ir[pc].data].scanFor(front)) - goto L_backtrack; - next(); - pc += IRL!(IR.CodepointSet); - break; - case IR.Trie: - if(atEnd || !re.tries[re.ir[pc].data][front]) - goto L_backtrack; - next(); - pc += IRL!(IR.Trie); - break; - case IR.Wordboundary: - dchar back; - DataIndex bi; - //at start & end of input - if(atStart && wordTrie[front]) - { - pc += IRL!(IR.Wordboundary); - break; - } - else if(atEnd && s.loopBack(index).nextChar(back, bi) - && wordTrie[back]) - { - pc += IRL!(IR.Wordboundary); - break; - } - else if(s.loopBack(index).nextChar(back, bi)) - { - bool af = wordTrie[front]; - bool ab = wordTrie[back]; - if(af ^ ab) - { - pc += IRL!(IR.Wordboundary); - break; - } - } - goto L_backtrack; - case IR.Notwordboundary: - dchar back; - DataIndex bi; - //at start & end of input - if(atStart && wordTrie[front]) - goto L_backtrack; - else if(atEnd && s.loopBack(index).nextChar(back, bi) - && wordTrie[back]) - goto L_backtrack; - else if(s.loopBack(index).nextChar(back, bi)) - { - bool af = wordTrie[front]; - bool ab = wordTrie[back]; - if(af ^ ab) - goto L_backtrack; - } - pc += IRL!(IR.Wordboundary); - break; - case IR.Bol: - dchar back; - DataIndex bi; - if(atStart) - pc += IRL!(IR.Bol); - else if((re.flags & RegexOption.multiline) - && s.loopBack(index).nextChar(back,bi) - && endOfLine(back, front == '\n')) - { - pc += IRL!(IR.Bol); - } - else - goto L_backtrack; - break; - case IR.Eol: - dchar back; - DataIndex bi; - debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]); - //no matching inside \r\n - if(atEnd || ((re.flags & RegexOption.multiline) - && endOfLine(front, s.loopBack(index).nextChar(back,bi) - && back == '\r'))) - { - pc += IRL!(IR.Eol); - } - else - goto L_backtrack; - break; - case IR.InfiniteStart, IR.InfiniteQStart: - trackers[infiniteNesting+1] = index; - pc += re.ir[pc].data + IRL!(IR.InfiniteStart); - //now pc is at end IR.Infininite(Q)End - uint len = re.ir[pc].data; - int test; - if(re.ir[pc].code == IR.InfiniteEnd) - { - test = quickTestFwd(pc+IRL!(IR.InfiniteEnd), front, re); - if(test >= 0) - pushState(pc+IRL!(IR.InfiniteEnd), counter); - infiniteNesting++; - pc -= len; - } - else - { - test = quickTestFwd(pc - len, front, re); - if(test >= 0) - { - infiniteNesting++; - pushState(pc - len, counter); - infiniteNesting--; - } - pc += IRL!(IR.InfiniteEnd); - } - break; - case IR.RepeatStart, IR.RepeatQStart: - pc += re.ir[pc].data + IRL!(IR.RepeatStart); - break; - case IR.RepeatEnd: - case IR.RepeatQEnd: - //len, step, min, max - uint len = re.ir[pc].data; - uint step = re.ir[pc+2].raw; - uint min = re.ir[pc+3].raw; - uint max = re.ir[pc+4].raw; - if(counter < min) - { - counter += step; - pc -= len; - } - else if(counter < max) - { - if(re.ir[pc].code == IR.RepeatEnd) - { - pushState(pc + IRL!(IR.RepeatEnd), counter%step); - counter += step; - pc -= len; - } - else - { - pushState(pc - len, counter + step); - counter = counter%step; - pc += IRL!(IR.RepeatEnd); - } - } - else - { - counter = counter%step; - pc += IRL!(IR.RepeatEnd); - } - break; - case IR.InfiniteEnd: - case IR.InfiniteQEnd: - uint len = re.ir[pc].data; - debug(std_regex_matcher) writeln("Infinited nesting:", infiniteNesting); - assert(infiniteNesting < trackers.length); - - if(trackers[infiniteNesting] == index) - {//source not consumed - pc += IRL!(IR.InfiniteEnd); - infiniteNesting--; - break; - } - else - trackers[infiniteNesting] = index; - int test; - if(re.ir[pc].code == IR.InfiniteEnd) - { - test = quickTestFwd(pc+IRL!(IR.InfiniteEnd), front, re); - if(test >= 0) - { - infiniteNesting--; - pushState(pc + IRL!(IR.InfiniteEnd), counter); - infiniteNesting++; - } - pc -= len; - } - else - { - test = quickTestFwd(pc-len, front, re); - if(test >= 0) - pushState(pc-len, counter); - pc += IRL!(IR.InfiniteEnd); - infiniteNesting--; - } - break; - case IR.OrEnd: - pc += IRL!(IR.OrEnd); - break; - case IR.OrStart: - pc += IRL!(IR.OrStart); - goto case; - case IR.Option: - uint len = re.ir[pc].data; - if(re.ir[pc+len].code == IR.GotoEndOr)//not a last one - { - pushState(pc + len + IRL!(IR.Option), counter); //remember 2nd branch - } - pc += IRL!(IR.Option); - break; - case IR.GotoEndOr: - pc = pc + re.ir[pc].data + IRL!(IR.GotoEndOr); - break; - case IR.GroupStart: - uint n = re.ir[pc].data; - matches[n].begin = index; - debug(std_regex_matcher) writefln("IR group #%u starts at %u", n, index); - pc += IRL!(IR.GroupStart); - break; - case IR.GroupEnd: - uint n = re.ir[pc].data; - matches[n].end = index; - debug(std_regex_matcher) writefln("IR group #%u ends at %u", n, index); - pc += IRL!(IR.GroupEnd); - break; - case IR.LookaheadStart: - case IR.NeglookaheadStart: - uint len = re.ir[pc].data; - auto save = index; - uint ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw; - auto mem = malloc(initialMemory(re))[0..initialMemory(re)]; - scope(exit) free(mem.ptr); - static if(Stream.isLoopback) - { - auto matcher = bwdMatcher(this, mem); - } - else - { - auto matcher = fwdMatcher(this, mem); - } - matcher.matches = matches[ms .. me]; - matcher.backrefed = backrefed.empty ? matches : backrefed; - matcher.re.ir = re.ir[pc+IRL!(IR.LookaheadStart) .. pc+IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd)]; - bool match = matcher.matchImpl() ^ (re.ir[pc].code == IR.NeglookaheadStart); - s.reset(save); - next(); - if(!match) - goto L_backtrack; - else - { - pc += IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd); - } - break; - case IR.LookbehindStart: - case IR.NeglookbehindStart: - uint len = re.ir[pc].data; - uint ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw; - auto mem = malloc(initialMemory(re))[0..initialMemory(re)]; - scope(exit) free(mem.ptr); - static if(Stream.isLoopback) - { - alias Matcher = BacktrackingMatcher!(Char, Stream); - auto matcher = Matcher(re, s, mem, front, index); - } - else - { - alias Matcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index))); - auto matcher = Matcher(re, s.loopBack(index), mem); - } - matcher.matches = matches[ms .. me]; - matcher.re.ir = re.ir[pc + IRL!(IR.LookbehindStart) .. pc + IRL!(IR.LookbehindStart) + len + IRL!(IR.LookbehindEnd)]; - matcher.backrefed = backrefed.empty ? matches : backrefed; - bool match = matcher.matchImpl() ^ (re.ir[pc].code == IR.NeglookbehindStart); - if(!match) - goto L_backtrack; - else - { - pc += IRL!(IR.LookbehindStart)+len+IRL!(IR.LookbehindEnd); - } - break; - case IR.Backref: - uint n = re.ir[pc].data; - auto referenced = re.ir[pc].localRef - ? s[matches[n].begin .. matches[n].end] - : s[backrefed[n].begin .. backrefed[n].end]; - while(!atEnd && !referenced.empty && front == referenced.front) - { - next(); - referenced.popFront(); - } - if(referenced.empty) - pc++; - else - goto L_backtrack; - break; - case IR.Nop: - pc += IRL!(IR.Nop); - break; - case IR.LookaheadEnd: - case IR.NeglookaheadEnd: - case IR.LookbehindEnd: - case IR.NeglookbehindEnd: - case IR.End: - return true; - default: - debug printBytecode(re.ir[0..$]); - assert(0); - L_backtrack: - if(!popState()) - { - s.reset(start); - return false; - } - } - } - } - assert(0); - } - - @property size_t stackAvail() - { - return memory.length - lastState; - } - - bool prevStack() - { - size_t* prev = memory.ptr-1; - prev = cast(size_t*)*prev;//take out hidden pointer - if(!prev) - return false; - free(memory.ptr);//last segment is freed in RegexMatch - immutable size = initialStack*(stateSize + 2*re.ngroup); - memory = prev[0..size]; - lastState = size; - return true; - } - - void stackPush(T)(T val) - if(!isDynamicArray!T) - { - *cast(T*)&memory[lastState] = val; - enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof; - lastState += delta; - debug(std_regex_matcher) writeln("push element SP= ", lastState); - } - - void stackPush(T)(T[] val) - { - static assert(T.sizeof % size_t.sizeof == 0); - (cast(T*)&memory[lastState])[0..val.length] - = val[0..$]; - lastState += val.length*(T.sizeof/size_t.sizeof); - debug(std_regex_matcher) writeln("push array SP= ", lastState); - } - - void stackPop(T)(ref T val) - if(!isDynamicArray!T) - { - enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof; - lastState -= delta; - val = *cast(T*)&memory[lastState]; - debug(std_regex_matcher) writeln("pop element SP= ", lastState); - } - - void stackPop(T)(T[] val) - { - stackPop(val); // call ref version - } - void stackPop(T)(ref T[] val) - { - lastState -= val.length*(T.sizeof/size_t.sizeof); - val[0..$] = (cast(T*)&memory[lastState])[0..val.length]; - debug(std_regex_matcher) writeln("pop array SP= ", lastState); - } - - static if(!CTregex) - { - //helper function, saves engine state - void pushState(uint pc, uint counter) - { - if(stateSize + trackers.length + matches.length > stackAvail) - { - newStack(); - lastState = 0; - } - *cast(State*)&memory[lastState] = - State(index, pc, counter, infiniteNesting); - lastState += stateSize; - memory[lastState .. lastState + 2 * matches.length] = (cast(size_t[])matches)[]; - lastState += 2*matches.length; - if(trackers.length) - { - memory[lastState .. lastState + trackers.length] = trackers[]; - lastState += trackers.length; - } - debug(std_regex_matcher) - writefln("Saved(pc=%s) front: %s src: %s", - pc, front, s[index..s.lastIndex]); - } - - //helper function, restores engine state - bool popState() - { - if(!lastState) - return prevStack(); - if (trackers.length) - { - lastState -= trackers.length; - trackers[] = memory[lastState .. lastState + trackers.length]; - } - lastState -= 2*matches.length; - auto pm = cast(size_t[])matches; - pm[] = memory[lastState .. lastState + 2 * matches.length]; - lastState -= stateSize; - State* state = cast(State*)&memory[lastState]; - index = state.index; - pc = state.pc; - counter = state.counter; - infiniteNesting = state.infiniteNesting; - debug(std_regex_matcher) - { - writefln("Restored matches", front, s[index .. s.lastIndex]); - foreach(i, m; matches) - writefln("Sub(%d) : %s..%s", i, m.begin, m.end); - } - s.reset(index); - next(); - debug(std_regex_matcher) - writefln("Backtracked (pc=%s) front: %s src: %s", - pc, front, s[index..s.lastIndex]); - return true; - } - } - } - } - -//very shitty string formatter, $$ replaced with next argument converted to string -@trusted string ctSub( U...)(string format, U args) -{ - bool seenDollar; - foreach(i, ch; format) - { - if(ch == '$') - { - if(seenDollar) - { - static if(args.length > 0) - { - return format[0 .. i - 1] ~ to!string(args[0]) - ~ ctSub(format[i + 1 .. $], args[1 .. $]); - } - else - assert(0); - } - else - seenDollar = true; - } - else - seenDollar = false; - - } - return format; -} - -//generate code for TypeTuple(S, S+1, S+2, ... E) -@system string ctGenSeq(int S, int E) -{ - string s = "alias Sequence = TypeTuple!("; - if(S < E) - s ~= to!string(S); - for(int i = S+1; i < E;i++) - { - s ~= ", "; - s ~= to!string(i); - } - return s ~");"; -} - -//alias to TypeTuple(S, S+1, S+2, ... E) -template Sequence(int S, int E) -{ - mixin(ctGenSeq(S,E)); -} - -struct CtContext -{ - //dirty flags - bool counter, infNesting; - // to make a unique advancement counter per nesting level of loops - int curInfLoop, nInfLoops; - //to mark the portion of matches to save - int match, total_matches; - int reserved; - - - //state of codegenerator - struct CtState - { - string code; - int addr; - } - - this(Char)(Regex!Char re) - { - match = 1; - reserved = 1; //first match is skipped - total_matches = re.ngroup; - } - - CtContext lookaround(uint s, uint e) - { - CtContext ct; - ct.total_matches = e - s; - ct.match = 1; - return ct; - } - - //restore state having current context - string restoreCode() - { - string text; - //stack is checked in L_backtrack - text ~= counter - ? " - stackPop(counter);" - : " - counter = 0;"; - if(infNesting) - { - text ~= ctSub(` - stackPop(trackers[0..$$]); - `, curInfLoop + 1); - } - if(match < total_matches) - { - text ~= ctSub(" - stackPop(matches[$$..$$]);", reserved, match); - text ~= ctSub(" - matches[$$..$] = typeof(matches[0]).init;", match); - } - else - text ~= ctSub(" - stackPop(matches[$$..$]);", reserved); - return text; - } - - //save state having current context - string saveCode(uint pc, string count_expr="counter") - { - string text = ctSub(" - if(stackAvail < $$*(Group!(DataIndex)).sizeof/size_t.sizeof + trackers.length + $$) - { - newStack(); - lastState = 0; - }", match - reserved, cast(int)counter + 2); - if(match < total_matches) - text ~= ctSub(" - stackPush(matches[$$..$$]);", reserved, match); - else - text ~= ctSub(" - stackPush(matches[$$..$]);", reserved); - if(infNesting) - { - text ~= ctSub(` - stackPush(trackers[0..$$]); - `, curInfLoop + 1); - } - text ~= counter ? ctSub(" - stackPush($$);", count_expr) : ""; - text ~= ctSub(" - stackPush(index); stackPush($$); \n", pc); - return text; - } - - // - CtState ctGenBlock(Bytecode[] ir, int addr) - { - CtState result; - result.addr = addr; - while(!ir.empty) - { - auto n = ctGenGroup(ir, result.addr); - result.code ~= n.code; - result.addr = n.addr; - } - return result; - } - - // - CtState ctGenGroup(ref Bytecode[] ir, int addr) - { - auto bailOut = "goto L_backtrack;"; - auto nextInstr = ctSub("goto case $$;", addr+1); - CtState r; - assert(!ir.empty); - switch(ir[0].code) - { - case IR.InfiniteStart, IR.InfiniteQStart, IR.RepeatStart, IR.RepeatQStart: - bool infLoop = - ir[0].code == IR.InfiniteStart || ir[0].code == IR.InfiniteQStart; - infNesting = infNesting || infLoop; - if(infLoop) - { - curInfLoop++; - nInfLoops = max(nInfLoops, curInfLoop+1); - } - counter = counter || - ir[0].code == IR.RepeatStart || ir[0].code == IR.RepeatQStart; - uint len = ir[0].data; - auto nir = ir[ir[0].length .. ir[0].length+len]; - r = ctGenBlock(nir, addr+1); - if(infLoop) - curInfLoop--; - //start/end codegen - //r.addr is at last test+ jump of loop, addr+1 is body of loop - nir = ir[ir[0].length + len .. $]; - r.code = ctGenFixupCode(ir[0..ir[0].length], addr, r.addr) ~ r.code; - r.code ~= ctGenFixupCode(nir, r.addr, addr+1); - r.addr += 2; //account end instruction + restore state - ir = nir; - break; - case IR.OrStart: - uint len = ir[0].data; - auto nir = ir[ir[0].length .. ir[0].length+len]; - r = ctGenAlternation(nir, addr); - ir = ir[ir[0].length + len .. $]; - assert(ir[0].code == IR.OrEnd); - ir = ir[ir[0].length..$]; - break; - case IR.LookaheadStart: - case IR.NeglookaheadStart: - case IR.LookbehindStart: - case IR.NeglookbehindStart: - uint len = ir[0].data; - bool behind = ir[0].code == IR.LookbehindStart || ir[0].code == IR.NeglookbehindStart; - bool negative = ir[0].code == IR.NeglookaheadStart || ir[0].code == IR.NeglookbehindStart; - string fwdType = "typeof(fwdMatcher(matcher, []))"; - string bwdType = "typeof(bwdMatcher(matcher, []))"; - string fwdCreate = "fwdMatcher(matcher, mem)"; - string bwdCreate = "bwdMatcher(matcher, mem)"; - uint start = IRL!(IR.LookbehindStart); - uint end = IRL!(IR.LookbehindStart)+len+IRL!(IR.LookaheadEnd); - CtContext context = lookaround(ir[1].raw, ir[2].raw); //split off new context - auto slice = ir[start .. end]; - r.code ~= ctSub(` - case $$: //fake lookaround "atom" - static if(typeof(matcher.s).isLoopback) - alias Lookaround = $$; - else - alias Lookaround = $$; - static bool matcher_$$(ref Lookaround matcher) @trusted - { - //(neg)lookaround piece start - $$ - //(neg)lookaround piece ends - } - auto save = index; - auto mem = malloc(initialMemory(re))[0..initialMemory(re)]; - scope(exit) free(mem.ptr); - static if(typeof(matcher.s).isLoopback) - auto lookaround = $$; - else - auto lookaround = $$; - lookaround.matches = matches[$$..$$]; - lookaround.backrefed = backrefed.empty ? matches : backrefed; - lookaround.nativeFn = &matcher_$$; //hookup closure's binary code - bool match = $$; - s.reset(save); - next(); - if(match) - $$ - else - $$`, addr, - behind ? fwdType : bwdType, behind ? bwdType : fwdType, - addr, context.ctGenRegEx(slice), - behind ? fwdCreate : bwdCreate, behind ? bwdCreate : fwdCreate, - ir[1].raw, ir[2].raw, //start - end of matches slice - addr, - negative ? "!lookaround.matchImpl()" : "lookaround.matchImpl()", - nextInstr, bailOut); - ir = ir[end .. $]; - r.addr = addr + 1; - break; - case IR.LookaheadEnd: case IR.NeglookaheadEnd: - case IR.LookbehindEnd: case IR.NeglookbehindEnd: - ir = ir[IRL!(IR.LookaheadEnd) .. $]; - r.addr = addr; - break; - default: - assert(ir[0].isAtom, text(ir[0].mnemonic)); - r = ctGenAtom(ir, addr); - } - return r; - } - - //generate source for bytecode contained in OrStart ... OrEnd - CtState ctGenAlternation(Bytecode[] ir, int addr) - { - CtState[] pieces; - CtState r; - enum optL = IRL!(IR.Option); - for(;;) - { - assert(ir[0].code == IR.Option); - auto len = ir[0].data; - if(optL+len < ir.length && ir[optL+len].code == IR.Option)//not a last option - { - auto nir = ir[optL .. optL+len-IRL!(IR.GotoEndOr)]; - r = ctGenBlock(nir, addr+2);//space for Option + restore state - //r.addr+1 to account GotoEndOr at end of branch - r.code = ctGenFixupCode(ir[0 .. ir[0].length], addr, r.addr+1) ~ r.code; - addr = r.addr+1;//leave space for GotoEndOr - pieces ~= r; - ir = ir[optL + len .. $]; - } - else - { - pieces ~= ctGenBlock(ir[optL..$], addr); - addr = pieces[$-1].addr; - break; - } - } - r = pieces[0]; - for(uint i = 1; i < pieces.length; i++) - { - r.code ~= ctSub(` - case $$: - goto case $$; `, pieces[i-1].addr, addr); - r.code ~= pieces[i].code; - } - r.addr = addr; - return r; - } - - // generate fixup code for instruction in ir, - // fixup means it has an alternative way for control flow - string ctGenFixupCode(Bytecode[] ir, int addr, int fixup) - { - return ctGenFixupCode(ir, addr, fixup); // call ref Bytecode[] version - } - string ctGenFixupCode(ref Bytecode[] ir, int addr, int fixup) - { - string r; - string testCode; - r = ctSub(` - case $$: debug(std_regex_matcher) writeln("#$$");`, - addr, addr); - switch(ir[0].code) - { - case IR.InfiniteStart, IR.InfiniteQStart: - r ~= ctSub( ` - trackers[$$] = DataIndex.max; - goto case $$;`, curInfLoop, fixup); - ir = ir[ir[0].length..$]; - break; - case IR.InfiniteEnd: - testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1); - r ~= ctSub( ` - if(trackers[$$] == index) - {//source not consumed - goto case $$; - } - trackers[$$] = index; - - $$ - { - $$ - } - goto case $$; - case $$: //restore state and go out of loop - $$ - goto case;`, curInfLoop, addr+2, - curInfLoop, testCode, saveCode(addr+1), - fixup, addr+1, restoreCode()); - ir = ir[ir[0].length..$]; - break; - case IR.InfiniteQEnd: - testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1); - auto altCode = testCode.length ? ctSub("else goto case $$;", fixup) : ""; - r ~= ctSub( ` - if(trackers[$$] == index) - {//source not consumed - goto case $$; - } - trackers[$$] = index; - - $$ - { - $$ - goto case $$; - } - $$ - case $$://restore state and go inside loop - $$ - goto case $$;`, curInfLoop, addr+2, - curInfLoop, testCode, saveCode(addr+1), - addr+2, altCode, addr+1, restoreCode(), fixup); - ir = ir[ir[0].length..$]; - break; - case IR.RepeatStart, IR.RepeatQStart: - r ~= ctSub( ` - goto case $$;`, fixup); - ir = ir[ir[0].length..$]; - break; - case IR.RepeatEnd, IR.RepeatQEnd: - //len, step, min, max - uint len = ir[0].data; - uint step = ir[2].raw; - uint min = ir[3].raw; - uint max = ir[4].raw; - r ~= ctSub(` - if(counter < $$) - { - debug(std_regex_matcher) writeln("RepeatEnd min case pc=", $$); - counter += $$; - goto case $$; - }`, min, addr, step, fixup); - if(ir[0].code == IR.RepeatEnd) - { - string counter_expr = ctSub("counter % $$", step); - r ~= ctSub(` - else if(counter < $$) - { - $$ - counter += $$; - goto case $$; - }`, max, saveCode(addr+1, counter_expr), step, fixup); - } - else - { - string counter_expr = ctSub("counter % $$", step); - r ~= ctSub(` - else if(counter < $$) - { - $$ - counter = counter % $$; - goto case $$; - }`, max, saveCode(addr+1,counter_expr), step, addr+2); - } - r ~= ctSub(` - else - { - counter = counter % $$; - goto case $$; - } - case $$: //restore state - $$ - goto case $$;`, step, addr+2, addr+1, restoreCode(), - ir[0].code == IR.RepeatEnd ? addr+2 : fixup ); - ir = ir[ir[0].length..$]; - break; - case IR.Option: - r ~= ctSub( ` - { - $$ - } - goto case $$; - case $$://restore thunk to go to the next group - $$ - goto case $$;`, saveCode(addr+1), addr+2, - addr+1, restoreCode(), fixup); - ir = ir[ir[0].length..$]; - break; - default: - assert(0, text(ir[0].mnemonic)); - } - return r; - } - - - string ctQuickTest(Bytecode[] ir, int id) - { - uint pc = 0; - while(pc < ir.length && ir[pc].isAtom) - { - if(ir[pc].code == IR.GroupStart || ir[pc].code == IR.GroupEnd) - { - pc++; - } - else if(ir[pc].code == IR.Backref) - break; - else - { - auto code = ctAtomCode(ir[pc..$], -1); - return ctSub(` - int test_$$() - { - $$ //$$ - } - if(test_$$() >= 0)`, id, code.ptr ? code : "return 0;", - ir[pc].mnemonic, id); - } - } - return ""; - } - - //process & generate source for simple bytecodes at front of ir using address addr - CtState ctGenAtom(ref Bytecode[] ir, int addr) - { - CtState result; - result.code = ctAtomCode(ir, addr); - ir.popFrontN(ir[0].code == IR.OrChar ? ir[0].sequence : ir[0].length); - result.addr = addr + 1; - return result; - } - - //D code for atom at ir using address addr, addr < 0 means quickTest - string ctAtomCode(Bytecode[] ir, int addr) - { - string code; - string bailOut, nextInstr; - if(addr < 0) - { - bailOut = "return -1;"; - nextInstr = "return 0;"; - } - else - { - bailOut = "goto L_backtrack;"; - nextInstr = ctSub("goto case $$;", addr+1); - code ~= ctSub( ` - case $$: debug(std_regex_matcher) writeln("#$$"); - `, addr, addr); - } - switch(ir[0].code) - { - case IR.OrChar://assumes IRL!(OrChar) == 1 - code ~= ctSub(` - if(atEnd) - $$`, bailOut); - uint len = ir[0].sequence; - for(uint i = 0; i < len; i++) - { - code ~= ctSub( ` - if(front == $$) - { - $$ - $$ - }`, ir[i].data, addr >= 0 ? "next();" :"", nextInstr); - } - code ~= ctSub( ` - $$`, bailOut); - break; - case IR.Char: - code ~= ctSub( ` - if(atEnd || front != $$) - $$ - $$ - $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); - break; - case IR.Any: - code ~= ctSub( ` - if(atEnd || (!(re.flags & RegexOption.singleline) - && (front == '\r' || front == '\n'))) - $$ - $$ - $$`, bailOut, addr >= 0 ? "next();" :"",nextInstr); - break; - case IR.CodepointSet: - code ~= ctSub( ` - if(atEnd || !re.charsets[$$].scanFor(front)) - $$ - $$ - $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); - break; - case IR.Trie: - code ~= ctSub( ` - if(atEnd || !re.tries[$$][front]) - $$ - $$ - $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); - break; - case IR.Wordboundary: - code ~= ctSub( ` - dchar back; - DataIndex bi; - if(atStart && wordTrie[front]) - { - $$ - } - else if(atEnd && s.loopBack(index).nextChar(back, bi) - && wordTrie[back]) - { - $$ - } - else if(s.loopBack(index).nextChar(back, bi)) - { - bool af = wordTrie[front]; - bool ab = wordTrie[back]; - if(af ^ ab) - { - $$ - } - } - $$`, nextInstr, nextInstr, nextInstr, bailOut); - break; - case IR.Notwordboundary: - code ~= ctSub( ` - dchar back; - DataIndex bi; - //at start & end of input - if(atStart && wordTrie[front]) - $$ - else if(atEnd && s.loopBack(index).nextChar(back, bi) - && wordTrie[back]) - $$ - else if(s.loopBack(index).nextChar(back, bi)) - { - bool af = wordTrie[front]; - bool ab = wordTrie[back]; - if(af ^ ab) - $$ - } - $$`, bailOut, bailOut, bailOut, nextInstr); - - break; - case IR.Bol: - code ~= ctSub(` - dchar back; - DataIndex bi; - if(atStart || ((re.flags & RegexOption.multiline) - && s.loopBack(index).nextChar(back,bi) - && endOfLine(back, front == '\n'))) - { - debug(std_regex_matcher) writeln("BOL matched"); - $$ - } - else - $$`, nextInstr, bailOut); - - break; - case IR.Eol: - code ~= ctSub(` - dchar back; - DataIndex bi; - debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]); - //no matching inside \r\n - if(atEnd || ((re.flags & RegexOption.multiline) - && endOfLine(front, s.loopBack(index).nextChar(back,bi) - && back == '\r'))) - { - debug(std_regex_matcher) writeln("EOL matched"); - $$ - } - else - $$`, nextInstr, bailOut); - - break; - case IR.GroupStart: - code ~= ctSub(` - matches[$$].begin = index; - $$`, ir[0].data, nextInstr); - match = ir[0].data+1; - break; - case IR.GroupEnd: - code ~= ctSub(` - matches[$$].end = index; - $$`, ir[0].data, nextInstr); - break; - case IR.Backref: - string mStr = "auto referenced = "; - mStr ~= ir[0].localRef - ? ctSub("s[matches[$$].begin .. matches[$$].end];", - ir[0].data, ir[0].data) - : ctSub("s[backrefed[$$].begin .. backrefed[$$].end];", - ir[0].data, ir[0].data); - code ~= ctSub( ` - $$ - while(!atEnd && !referenced.empty && front == referenced.front) - { - next(); - referenced.popFront(); - } - if(referenced.empty) - $$ - else - $$`, mStr, nextInstr, bailOut); - break; - case IR.Nop: - case IR.End: - break; - default: - assert(0, text(ir[0].mnemonic, " is not supported yet")); - } - return code; - } - - //generate D code for the whole regex - public string ctGenRegEx(Bytecode[] ir) - { - auto bdy = ctGenBlock(ir, 0); - auto r = ` - with(matcher) - { - pc = 0; - counter = 0; - lastState = 0; - auto start = s._index;`; - r ~= ` - goto StartLoop; - debug(std_regex_matcher) writeln("Try CT matching starting at ",s[index..s.lastIndex]); - L_backtrack: - if(lastState || prevStack()) - { - stackPop(pc); - stackPop(index); - s.reset(index); - next(); - } - else - { - s.reset(start); - return false; - } - StartLoop: - switch(pc) - { - `; - r ~= bdy.code; - r ~= ctSub(` - case $$: break;`,bdy.addr); - r ~= ` - default: - assert(0); - } - return true; - } - `; - return r; - } - -} - -string ctGenRegExCode(Char)(Regex!Char re) -{ - auto context = CtContext(re); - return context.ctGenRegEx(re.ir); -} - -//State of VM thread -struct Thread(DataIndex) -{ - Thread* next; //intrusive linked list - uint pc; - uint counter; //loop counter - uint uopCounter; //counts micro operations inside one macro instruction (e.g. BackRef) - Group!DataIndex[1] matches; -} - -//head-tail singly-linked list -struct ThreadList(DataIndex) -{ - Thread!DataIndex* tip = null, toe = null; - //add new thread to the start of list - void insertFront(Thread!DataIndex* t) - { - if(tip) - { - t.next = tip; - tip = t; - } - else - { - t.next = null; - tip = toe = t; - } - } - //add new thread to the end of list - void insertBack(Thread!DataIndex* t) - { - if(toe) - { - toe.next = t; - toe = t; - } - else - tip = toe = t; - toe.next = null; - } - //move head element out of list - Thread!DataIndex* fetch() - { - auto t = tip; - if(tip == toe) - tip = toe = null; - else - tip = tip.next; - return t; - } - //non-destructive iteration of ThreadList - struct ThreadRange - { - const(Thread!DataIndex)* ct; - this(ThreadList tlist){ ct = tlist.tip; } - @property bool empty(){ return ct is null; } - @property const(Thread!DataIndex)* front(){ return ct; } - @property popFront() - { - assert(ct); - ct = ct.next; - } - } - @property bool empty() - { - return tip == null; - } - ThreadRange opSlice() - { - return ThreadRange(this); - } -} - -//direction parameter for thompson one-shot match evaluator -enum OneShot { Fwd, Bwd }; - -/+ - Thomspon matcher does all matching in lockstep, - never looking at the same char twice -+/ -@trusted struct ThompsonMatcher(Char, Stream = Input!Char) - if(is(Char : dchar)) -{ - alias DataIndex = Stream.DataIndex; - Thread!DataIndex* freelist; - ThreadList!DataIndex clist, nlist; - DataIndex[] merge; - Group!DataIndex[] backrefed; - Regex!Char re; //regex program - Stream s; - dchar front; - DataIndex index; - DataIndex genCounter; //merge trace counter, goes up on every dchar - size_t[size_t] subCounters; //a table of gen counter per sub-engine: PC -> counter - size_t threadSize; - bool matched; - bool exhausted; - static if(__traits(hasMember,Stream, "search")) - { - enum kicked = true; - } - else - enum kicked = false; - - static size_t getThreadSize(const ref Regex!Char re) - { - return re.ngroup - ? (Thread!DataIndex).sizeof + (re.ngroup-1)*(Group!DataIndex).sizeof - : (Thread!DataIndex).sizeof - (Group!DataIndex).sizeof; - } - - static size_t initialMemory(const ref Regex!Char re) - { - return getThreadSize(re)*re.threadCount + re.hotspotTableSize*size_t.sizeof; - } - - //true if it's start of input - @property bool atStart(){ return index == 0; } - - //true if it's end of input - @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } - - bool next() - { - if(!s.nextChar(front, index)) - { - index = s.lastIndex; - return false; - } - return true; - } - - static if(kicked) - { - bool search() - { - - if(!s.search(re.kickstart, front, index)) - { - index = s.lastIndex; - return false; - } - return true; - } - } - - void initExternalMemory(void[] memory) - { - threadSize = getThreadSize(re); - prepareFreeList(re.threadCount, memory); - if(re.hotspotTableSize) - { - merge = arrayInChunk!(DataIndex)(re.hotspotTableSize, memory); - merge[] = 0; - } - } - - this()(Regex!Char program, Stream stream, void[] memory) - { - re = program; - s = stream; - initExternalMemory(memory); - genCounter = 0; - } - - this(S)(ref ThompsonMatcher!(Char,S) matcher, Bytecode[] piece, Stream stream) - { - s = stream; - re = matcher.re; - re.ir = piece; - threadSize = matcher.threadSize; - merge = matcher.merge; - freelist = matcher.freelist; - front = matcher.front; - index = matcher.index; - } - - auto fwdMatcher()(Bytecode[] piece, size_t counter) - { - auto m = ThompsonMatcher!(Char, Stream)(this, piece, s); - m.genCounter = counter; - return m; - } - - auto bwdMatcher()(Bytecode[] piece, size_t counter) - { - alias BackLooper = typeof(s.loopBack(index)); - auto m = ThompsonMatcher!(Char, BackLooper)(this, piece, s.loopBack(index)); - m.genCounter = counter; - m.next(); - return m; - } - - auto dupTo(void[] memory) - { - typeof(this) tmp = this;//bitblit - tmp.initExternalMemory(memory); - tmp.genCounter = 0; - return tmp; - } - - enum MatchResult{ - NoMatch, - PartialMatch, - Match, - } - - bool match(Group!DataIndex[] matches) - { - debug(std_regex_matcher) - writeln("------------------------------------------"); - if(exhausted) - { - return false; - } - if(re.flags & RegexInfo.oneShot) - { - next(); - exhausted = true; - return matchOneShot(matches)==MatchResult.Match; - } - static if(kicked) - if(!re.kickstart.empty) - return matchImpl!(true)(matches); - return matchImpl!(false)(matches); - } - - //match the input and fill matches - bool matchImpl(bool withSearch)(Group!DataIndex[] matches) - { - if(!matched && clist.empty) - { - static if(withSearch) - search(); - else - next(); - } - else//char in question is fetched in prev call to match - { - matched = false; - } - - if(!atEnd)//if no char - for(;;) - { - genCounter++; - debug(std_regex_matcher) - { - writefln("Threaded matching threads at %s", s[index..s.lastIndex]); - foreach(t; clist[]) - { - assert(t); - writef("pc=%s ",t.pc); - write(t.matches); - writeln(); - } - } - for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) - { - eval!true(t, matches); - } - if(!matched)//if we already have match no need to push the engine - eval!true(createStart(index), matches);//new thread staring at this position - else if(nlist.empty) - { - debug(std_regex_matcher) writeln("Stopped matching before consuming full input"); - break;//not a partial match for sure - } - clist = nlist; - nlist = (ThreadList!DataIndex).init; - if(clist.tip is null) - { - static if(withSearch) - { - if(!search()) - break; - } - else - { - if(!next()) - break; - } - } - else if(!next()) - { - if (!atEnd) return false; - exhausted = true; - break; - } - } - - genCounter++; //increment also on each end - debug(std_regex_matcher) writefln("Threaded matching threads at end"); - //try out all zero-width posibilities - for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) - { - eval!false(t, matches); - } - if(!matched) - eval!false(createStart(index), matches);//new thread starting at end of input - if(matched) - {//in case NFA found match along the way - //and last possible longer alternative ultimately failed - s.reset(matches[0].end);//reset to last successful match - next();//and reload front character - //--- here the exact state of stream was restored --- - exhausted = atEnd || !(re.flags & RegexOption.global); - //+ empty match advances the input - if(!exhausted && matches[0].begin == matches[0].end) - next(); - } - return matched; - } - - /+ - handle succesful threads - +/ - void finish(const(Thread!DataIndex)* t, Group!DataIndex[] matches) - { - matches.ptr[0..re.ngroup] = t.matches.ptr[0..re.ngroup]; - debug(std_regex_matcher) - { - writef("FOUND pc=%s prog_len=%s", - t.pc, re.ir.length); - if(!matches.empty) - writefln(": %s..%s", matches[0].begin, matches[0].end); - foreach(v; matches) - writefln("%d .. %d", v.begin, v.end); - } - matched = true; - } - - /+ - match thread against codepoint, cutting trough all 0-width instructions - and taking care of control flow, then add it to nlist - +/ - void eval(bool withInput)(Thread!DataIndex* t, Group!DataIndex[] matches) - { - ThreadList!DataIndex worklist; - debug(std_regex_matcher) writeln("---- Evaluating thread"); - for(;;) - { - debug(std_regex_matcher) - { - writef("\tpc=%s [", t.pc); - foreach(x; worklist[]) - writef(" %s ", x.pc); - writeln("]"); - } - switch(re.ir[t.pc].code) - { - case IR.End: - finish(t, matches); - matches[0].end = index; //fix endpoint of the whole match - recycle(t); - //cut off low priority threads - recycle(clist); - recycle(worklist); - debug(std_regex_matcher) writeln("Finished thread ", matches); - return; - case IR.Wordboundary: - dchar back; - DataIndex bi; - //at start & end of input - if(atStart && wordTrie[front]) - { - t.pc += IRL!(IR.Wordboundary); - break; - } - else if(atEnd && s.loopBack(index).nextChar(back, bi) - && wordTrie[back]) - { - t.pc += IRL!(IR.Wordboundary); - break; - } - else if(s.loopBack(index).nextChar(back, bi)) - { - bool af = wordTrie[front]; - bool ab = wordTrie[back]; - if(af ^ ab) - { - t.pc += IRL!(IR.Wordboundary); - break; - } - } - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - case IR.Notwordboundary: - dchar back; - DataIndex bi; - //at start & end of input - if(atStart && wordTrie[front]) - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - else if(atEnd && s.loopBack(index).nextChar(back, bi) - && wordTrie[back]) - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - else if(s.loopBack(index).nextChar(back, bi)) - { - bool af = wordTrie[front]; - bool ab = wordTrie[back] != 0; - if(af ^ ab) - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - } - t.pc += IRL!(IR.Wordboundary); - break; - case IR.Bol: - dchar back; - DataIndex bi; - if(atStart - ||( (re.flags & RegexOption.multiline) - && s.loopBack(index).nextChar(back,bi) - && startOfLine(back, front == '\n'))) - { - t.pc += IRL!(IR.Bol); - } - else - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - } - break; - case IR.Eol: - debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]); - dchar back; - DataIndex bi; - //no matching inside \r\n - if(atEnd || ((re.flags & RegexOption.multiline) - && endOfLine(front, s.loopBack(index).nextChar(back, bi) - && back == '\r'))) - { - t.pc += IRL!(IR.Eol); - } - else - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - } - break; - case IR.InfiniteStart, IR.InfiniteQStart: - t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart); - goto case IR.InfiniteEnd; //both Q and non-Q - case IR.RepeatStart, IR.RepeatQStart: - t.pc += re.ir[t.pc].data + IRL!(IR.RepeatStart); - goto case IR.RepeatEnd; //both Q and non-Q - case IR.RepeatEnd: - case IR.RepeatQEnd: - //len, step, min, max - uint len = re.ir[t.pc].data; - uint step = re.ir[t.pc+2].raw; - uint min = re.ir[t.pc+3].raw; - if(t.counter < min) - { - t.counter += step; - t.pc -= len; - break; - } - if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) - { - debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", - t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); - merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; - } - else - { - debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", - t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - uint max = re.ir[t.pc+4].raw; - if(t.counter < max) - { - if(re.ir[t.pc].code == IR.RepeatEnd) - { - //queue out-of-loop thread - worklist.insertFront(fork(t, t.pc + IRL!(IR.RepeatEnd), t.counter % step)); - t.counter += step; - t.pc -= len; - } - else - { - //queue into-loop thread - worklist.insertFront(fork(t, t.pc - len, t.counter + step)); - t.counter %= step; - t.pc += IRL!(IR.RepeatEnd); - } - } - else - { - t.counter %= step; - t.pc += IRL!(IR.RepeatEnd); - } - break; - case IR.InfiniteEnd: - case IR.InfiniteQEnd: - if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) - { - debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", - t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); - merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; - } - else - { - debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", - t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - uint len = re.ir[t.pc].data; - uint pc1, pc2; //branches to take in priority order - if(re.ir[t.pc].code == IR.InfiniteEnd) - { - pc1 = t.pc - len; - pc2 = t.pc + IRL!(IR.InfiniteEnd); - } - else - { - pc1 = t.pc + IRL!(IR.InfiniteEnd); - pc2 = t.pc - len; - } - static if(withInput) - { - int test = quickTestFwd(pc1, front, re); - if(test >= 0) - { - worklist.insertFront(fork(t, pc2, t.counter)); - t.pc = pc1; - } - else - t.pc = pc2; - } - else - { - worklist.insertFront(fork(t, pc2, t.counter)); - t.pc = pc1; - } - break; - case IR.OrEnd: - if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) - { - debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", - t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] ); - merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; - t.pc += IRL!(IR.OrEnd); - } - else - { - debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", - t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] ); - recycle(t); - t = worklist.fetch(); - if(!t) - return; - } - break; - case IR.OrStart: - t.pc += IRL!(IR.OrStart); - goto case; - case IR.Option: - uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option); - //queue next Option - if(re.ir[next].code == IR.Option) - { - worklist.insertFront(fork(t, next, t.counter)); - } - t.pc += IRL!(IR.Option); - break; - case IR.GotoEndOr: - t.pc = t.pc + re.ir[t.pc].data + IRL!(IR.GotoEndOr); - goto case IR.OrEnd; - case IR.GroupStart: - uint n = re.ir[t.pc].data; - t.matches.ptr[n].begin = index; - t.pc += IRL!(IR.GroupStart); - break; - case IR.GroupEnd: - uint n = re.ir[t.pc].data; - t.matches.ptr[n].end = index; - t.pc += IRL!(IR.GroupEnd); - break; - case IR.Backref: - uint n = re.ir[t.pc].data; - Group!DataIndex* source = re.ir[t.pc].localRef ? t.matches.ptr : backrefed.ptr; - assert(source); - if(source[n].begin == source[n].end)//zero-width Backref! - { - t.pc += IRL!(IR.Backref); - } - else static if(withInput) - { - size_t idx = source[n].begin + t.uopCounter; - size_t end = source[n].end; - if(s[idx..end].front == front) - { - t.uopCounter += std.utf.stride(s[idx..end], 0); - if(t.uopCounter + source[n].begin == source[n].end) - {//last codepoint - t.pc += IRL!(IR.Backref); - t.uopCounter = 0; - } - nlist.insertBack(t); - } - else - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - else - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - break; - case IR.LookbehindStart: - case IR.NeglookbehindStart: - uint len = re.ir[t.pc].data; - uint ms = re.ir[t.pc + 1].raw, me = re.ir[t.pc + 2].raw; - uint end = t.pc + len + IRL!(IR.LookbehindEnd) + IRL!(IR.LookbehindStart); - bool positive = re.ir[t.pc].code == IR.LookbehindStart; - static if(Stream.isLoopback) - auto matcher = fwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); - else - auto matcher = bwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); - matcher.re.ngroup = me - ms; - matcher.backrefed = backrefed.empty ? t.matches : backrefed; - //backMatch - auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookbehindStart)); - freelist = matcher.freelist; - subCounters[t.pc] = matcher.genCounter; - if((mRes == MatchResult.Match) ^ positive) - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - else - t.pc = end; - break; - case IR.LookaheadStart: - case IR.NeglookaheadStart: - auto save = index; - uint len = re.ir[t.pc].data; - uint ms = re.ir[t.pc+1].raw, me = re.ir[t.pc+2].raw; - uint end = t.pc+len+IRL!(IR.LookaheadEnd)+IRL!(IR.LookaheadStart); - bool positive = re.ir[t.pc].code == IR.LookaheadStart; - static if(Stream.isLoopback) - auto matcher = bwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); - else - auto matcher = fwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); - matcher.re.ngroup = me - ms; - matcher.backrefed = backrefed.empty ? t.matches : backrefed; - auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookaheadStart)); - freelist = matcher.freelist; - subCounters[t.pc] = matcher.genCounter; - s.reset(index); - next(); - if((mRes == MatchResult.Match) ^ positive) - { - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - } - else - t.pc = end; - break; - case IR.LookaheadEnd: - case IR.NeglookaheadEnd: - case IR.LookbehindEnd: - case IR.NeglookbehindEnd: - finish(t, matches.ptr[0 .. re.ngroup]); - recycle(t); - //cut off low priority threads - recycle(clist); - recycle(worklist); - return; - case IR.Nop: - t.pc += IRL!(IR.Nop); - break; - - static if(withInput) - { - case IR.OrChar: - uint len = re.ir[t.pc].sequence; - uint end = t.pc + len; - static assert(IRL!(IR.OrChar) == 1); - for(; t.pc < end; t.pc++) - if(re.ir[t.pc].data == front) - break; - if(t.pc != end) - { - t.pc = end; - nlist.insertBack(t); - } - else - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - case IR.Char: - if(front == re.ir[t.pc].data) - { - t.pc += IRL!(IR.Char); - nlist.insertBack(t); - } - else - recycle(t); - t = worklist.fetch(); - if(!t) - return; - break; - case IR.Any: - t.pc += IRL!(IR.Any); - if(!(re.flags & RegexOption.singleline) - && (front == '\r' || front == '\n')) - recycle(t); - else - nlist.insertBack(t); - t = worklist.fetch(); - if(!t) - return; - break; - case IR.CodepointSet: - if(re.charsets[re.ir[t.pc].data].scanFor(front)) - { - t.pc += IRL!(IR.CodepointSet); - nlist.insertBack(t); - } - else - { - recycle(t); - } - t = worklist.fetch(); - if(!t) - return; - break; - case IR.Trie: - if(re.tries[re.ir[t.pc].data][front]) - { - t.pc += IRL!(IR.Trie); - nlist.insertBack(t); - } - else - { - recycle(t); - } - t = worklist.fetch(); - if(!t) - return; - break; - default: - assert(0, "Unrecognized instruction " ~ re.ir[t.pc].mnemonic); - } - else - { - default: - recycle(t); - t = worklist.fetch(); - if(!t) - return; - } - } - } - - } - enum uint RestartPc = uint.max; - //match the input, evaluating IR without searching - MatchResult matchOneShot(Group!DataIndex[] matches, uint startPc = 0) - { - debug(std_regex_matcher) - { - writefln("---------------single shot match ----------------- "); - } - alias evalFn = eval; - assert(clist == (ThreadList!DataIndex).init || startPc == RestartPc); // incorrect after a partial match - assert(nlist == (ThreadList!DataIndex).init || startPc == RestartPc); - if(!atEnd)//if no char - { - debug(std_regex_matcher) - { - writefln("-- Threaded matching threads at %s", s[index..s.lastIndex]); - } - if(startPc!=RestartPc) - { - auto startT = createStart(index, startPc); - genCounter++; - evalFn!true(startT, matches); - } - for(;;) - { - debug(std_regex_matcher) writeln("\n-- Started iteration of main cycle"); - genCounter++; - debug(std_regex_matcher) - { - foreach(t; clist[]) - { - assert(t); - } - } - for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) - { - evalFn!true(t, matches); - } - if(nlist.empty) - { - debug(std_regex_matcher) writeln("Stopped matching before consuming full input"); - break;//not a partial match for sure - } - clist = nlist; - nlist = (ThreadList!DataIndex).init; - if(!next()) - { - if (!atEnd) return MatchResult.PartialMatch; - break; - } - debug(std_regex_matcher) writeln("-- Ended iteration of main cycle\n"); - } - } - genCounter++; //increment also on each end - debug(std_regex_matcher) writefln("-- Matching threads at end"); - //try out all zero-width posibilities - for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) - { - evalFn!false(t, matches); - } - if(!matched) - evalFn!false(createStart(index, startPc), matches); - - return (matched?MatchResult.Match:MatchResult.NoMatch); - } - - //get a dirty recycled Thread - Thread!DataIndex* allocate() - { - assert(freelist, "not enough preallocated memory"); - Thread!DataIndex* t = freelist; - freelist = freelist.next; - return t; - } - - //link memory into a free list of Threads - void prepareFreeList(size_t size, ref void[] memory) - { - void[] mem = memory[0 .. threadSize*size]; - memory = memory[threadSize * size .. $]; - freelist = cast(Thread!DataIndex*)&mem[0]; - size_t i; - for(i = threadSize; i < threadSize*size; i += threadSize) - (cast(Thread!DataIndex*)&mem[i-threadSize]).next = cast(Thread!DataIndex*)&mem[i]; - (cast(Thread!DataIndex*)&mem[i-threadSize]).next = null; - } - - //dispose a thread - void recycle(Thread!DataIndex* t) - { - t.next = freelist; - freelist = t; - } - - //dispose list of threads - void recycle(ref ThreadList!DataIndex list) - { - auto t = list.tip; - while(t) - { - auto next = t.next; - recycle(t); - t = next; - } - list = list.init; - } - - //creates a copy of master thread with given pc - Thread!DataIndex* fork(Thread!DataIndex* master, uint pc, uint counter) - { - auto t = allocate(); - t.matches.ptr[0..re.ngroup] = master.matches.ptr[0..re.ngroup]; - t.pc = pc; - t.counter = counter; - t.uopCounter = 0; - return t; - } - - //creates a start thread - Thread!DataIndex* createStart(DataIndex index, uint pc = 0) - { - auto t = allocate(); - t.matches.ptr[0..re.ngroup] = (Group!DataIndex).init; - t.matches[0].begin = index; - t.pc = pc; - t.counter = 0; - t.uopCounter = 0; - return t; - } -} - -/++ - $(D Captures) object contains submatches captured during a call - to $(D match) or iteration over $(D RegexMatch) range. - - First element of range is the whole match. -+/ -@trusted public struct Captures(R, DIndex = size_t) - if(isSomeString!R) -{//@trusted because of union inside - alias DataIndex = DIndex; - alias String = R; -private: - R _input; - bool _empty; - enum smallString = 3; - union - { - Group!DataIndex[] big_matches; - Group!DataIndex[smallString] small_matches; - } - uint _f, _b; - uint _ngroup; - NamedGroup[] _names; - - this()(R input, uint ngroups, NamedGroup[] named) - { - _input = input; - _ngroup = ngroups; - _names = named; - newMatches(); - _b = _ngroup; - _f = 0; - } - - this(alias Engine)(ref RegexMatch!(R,Engine) rmatch) - { - _input = rmatch._input; - _ngroup = rmatch._engine.re.ngroup; - _names = rmatch._engine.re.dict; - newMatches(); - _b = _ngroup; - _f = 0; - } - - @property Group!DataIndex[] matches() - { - return _ngroup > smallString ? big_matches : small_matches[0 .. _ngroup]; - } - - void newMatches() - { - if(_ngroup > smallString) - big_matches = new Group!DataIndex[_ngroup]; - } - -public: - ///Slice of input prior to the match. - @property R pre() - { - return _empty ? _input[] : _input[0 .. matches[0].begin]; - } - - ///Slice of input immediately after the match. - @property R post() - { - return _empty ? _input[] : _input[matches[0].end .. $]; - } - - ///Slice of matched portion of input. - @property R hit() - { - assert(!_empty); - return _input[matches[0].begin .. matches[0].end]; - } - - ///Range interface. - @property R front() - { - assert(!empty); - return _input[matches[_f].begin .. matches[_f].end]; - } - - ///ditto - @property R back() - { - assert(!empty); - return _input[matches[_b - 1].begin .. matches[_b - 1].end]; - } - - ///ditto - void popFront() - { - assert(!empty); - ++_f; - } - - ///ditto - void popBack() - { - assert(!empty); - --_b; - } - - ///ditto - @property bool empty() const { return _empty || _f >= _b; } - - ///ditto - R opIndex()(size_t i) /*const*/ //@@@BUG@@@ - { - assert(_f + i < _b,text("requested submatch number ", i," is out of range")); - assert(matches[_f + i].begin <= matches[_f + i].end, - text("wrong match: ", matches[_f + i].begin, "..", matches[_f + i].end)); - return _input[matches[_f + i].begin .. matches[_f + i].end]; - } - - /++ - Explicit cast to bool. - Useful as a shorthand for !(x.empty) in if and assert statements. - - --- - import std.regex; - - assert(!matchFirst("nothing", "something")); - --- - +/ - - @safe bool opCast(T:bool)() const nothrow { return !empty; } - - /++ - Lookup named submatch. - - --- - import std.regex; - import std.range; - - auto c = matchFirst("a = 42;", regex(`(?P\w+)\s*=\s*(?P\d+);`)); - assert(c["var"] == "a"); - assert(c["value"] == "42"); - popFrontN(c, 2); - //named groups are unaffected by range primitives - assert(c["var"] =="a"); - assert(c.front == "42"); - ---- - +/ - R opIndex(String)(String i) /*const*/ //@@@BUG@@@ - if(isSomeString!String) - { - size_t index = lookupNamedGroup(_names, i); - return _input[matches[index].begin .. matches[index].end]; - } - - ///Number of matches in this object. - @property size_t length() const { return _empty ? 0 : _b - _f; } - - ///A hook for compatibility with original std.regex. - @property ref captures(){ return this; } -} - -/// -unittest -{ - auto c = matchFirst("@abc#", regex(`(\w)(\w)(\w)`)); - assert(c.pre == "@"); // Part of input preceding match - assert(c.post == "#"); // Immediately after match - assert(c.hit == c[0] && c.hit == "abc"); // The whole match - assert(c[2] == "b"); - assert(c.front == "abc"); - c.popFront(); - assert(c.front == "a"); - assert(c.back == "c"); - c.popBack(); - assert(c.back == "b"); - popFrontN(c, 2); - assert(c.empty); - - assert(!matchFirst("nothing", "something")); -} - -/++ - A regex engine state, as returned by $(D match) family of functions. - - Effectively it's a forward range of Captures!R, produced - by lazily searching for matches in a given input. - - $(D alias Engine) specifies an engine type to use during matching, - and is automatically deduced in a call to $(D match)/$(D bmatch). -+/ -@trusted public struct RegexMatch(R, alias Engine = ThompsonMatcher) - if(isSomeString!R) -{ -private: - alias Char = BasicElementOf!R; - alias EngineType = Engine!Char; - EngineType _engine; - R _input; - Captures!(R,EngineType.DataIndex) _captures; - void[] _memory;//is ref-counted - - this(RegEx)(R input, RegEx prog) - { - _input = input; - immutable size = EngineType.initialMemory(prog)+size_t.sizeof; - _memory = (enforce(malloc(size))[0..size]); - scope(failure) free(_memory.ptr); - *cast(size_t*)_memory.ptr = 1; - _engine = EngineType(prog, Input!Char(input), _memory[size_t.sizeof..$]); - static if(is(RegEx == StaticRegex!(BasicElementOf!R))) - _engine.nativeFn = prog.nativeFn; - _captures = Captures!(R,EngineType.DataIndex)(this); - _captures._empty = !_engine.match(_captures.matches); - debug(std_regex_allocation) writefln("RefCount (ctor): %x %d", _memory.ptr, counter); - } - - @property ref size_t counter(){ return *cast(size_t*)_memory.ptr; } -public: - this(this) - { - if(_memory.ptr) - { - ++counter; - debug(std_regex_allocation) writefln("RefCount (postblit): %x %d", - _memory.ptr, *cast(size_t*)_memory.ptr); - } - } - - ~this() - { - if(_memory.ptr && --*cast(size_t*)_memory.ptr == 0) - { - debug(std_regex_allocation) writefln("RefCount (dtor): %x %d", - _memory.ptr, *cast(size_t*)_memory.ptr); - free(cast(void*)_memory.ptr); - } - } - - ///Shorthands for front.pre, front.post, front.hit. - @property R pre() - { - return _captures.pre; - } - - ///ditto - @property R post() - { - return _captures.post; - } - - ///ditto - @property R hit() - { - return _captures.hit; - } - - /++ - Functionality for processing subsequent matches of global regexes via range interface: - --- - import std.regex; - auto m = matchAll("Hello, world!", regex(`\w+`)); - assert(m.front.hit == "Hello"); - m.popFront(); - assert(m.front.hit == "world"); - m.popFront(); - assert(m.empty); - --- - +/ - @property auto front() - { - return _captures; - } - - ///ditto - void popFront() - { - - if(counter != 1) - {//do cow magic first - counter--;//we abandon this reference - immutable size = EngineType.initialMemory(_engine.re)+size_t.sizeof; - _memory = (enforce(malloc(size))[0..size]); - _engine = _engine.dupTo(_memory[size_t.sizeof..size]); - counter = 1;//points to new chunk - } - //previous _captures can have escaped references from Capture object - _captures.newMatches(); - _captures._empty = !_engine.match(_captures.matches); - } - - ///ditto - auto save(){ return this; } - - ///Test if this match object is empty. - @property bool empty(){ return _captures._empty; } - - ///Same as !(x.empty), provided for its convenience in conditional statements. - T opCast(T:bool)(){ return !empty; } - - /// Same as .front, provided for compatibility with original std.regex. - @property auto captures(){ return _captures; } - -} - -private @trusted auto matchOnce(alias Engine, RegEx, R)(R input, RegEx re) -{ - alias Char = BasicElementOf!R; - alias EngineType = Engine!Char; - - size_t size = EngineType.initialMemory(re); - void[] memory = enforce(malloc(size))[0..size]; - scope(exit) free(memory.ptr); - auto captures = Captures!(R, EngineType.DataIndex)(input, re.ngroup, re.dict); - auto engine = EngineType(re, Input!Char(input), memory); - static if(is(RegEx == StaticRegex!(BasicElementOf!R))) - engine.nativeFn = re.nativeFn; - captures._empty = !engine.match(captures.matches); - return captures; -} - -private auto matchMany(alias Engine, RegEx, R)(R input, RegEx re) -{ - re.flags |= RegexOption.global; - return RegexMatch!(R, Engine)(input, re); -} - -unittest -{ - //sanity checks for new API - auto re = regex("abc"); - assert(!"abc".matchOnce!(ThompsonMatcher)(re).empty); - assert("abc".matchOnce!(ThompsonMatcher)(re)[0] == "abc"); -} -/++ - Compile regular expression pattern for the later execution. - Returns: $(D Regex) object that works on inputs having - the same character width as $(D pattern). - - Params: - pattern = Regular expression - flags = The _attributes (g, i, m and x accepted) - - Throws: $(D RegexException) if there were any errors during compilation. -+/ -@trusted public auto regex(S)(S pattern, const(char)[] flags="") - if(isSomeString!(S)) -{ - enum cacheSize = 8; //TODO: invent nice interface to control regex caching - if(__ctfe) - return regexImpl(pattern, flags); - return memoize!(regexImpl!S, cacheSize)(pattern, flags); -} - -public auto regexImpl(S)(S pattern, const(char)[] flags="") - if(isSomeString!(S)) -{ - auto parser = Parser!(Unqual!(typeof(pattern)))(pattern, flags); - auto r = parser.program; - return r; -} - - -template ctRegexImpl(alias pattern, string flags=[]) -{ - enum r = regex(pattern, flags); - alias Char = BasicElementOf!(typeof(pattern)); - enum source = ctGenRegExCode(r); - alias Matcher = BacktrackingMatcher!(true); - @trusted bool func(ref Matcher!Char matcher) - { - debug(std_regex_ctr) pragma(msg, source); - mixin(source); - } - enum nr = StaticRegex!Char(r, &func); -} - -/++ - Compile regular expression using CTFE - and generate optimized native machine code for matching it. - - Returns: StaticRegex object for faster matching. - - Params: - pattern = Regular expression - flags = The _attributes (g, i, m and x accepted) -+/ -public enum ctRegex(alias pattern, alias flags=[]) = ctRegexImpl!(pattern, flags).nr; - -enum isRegexFor(RegEx, R) = is(RegEx == Regex!(BasicElementOf!R)) - || is(RegEx == StaticRegex!(BasicElementOf!R)); - -/++ - Start matching $(D input) to regex pattern $(D re), - using Thompson NFA matching scheme. - - The use of this function is $(RED discouraged) - use either of - $(LREF matchAll) or $(LREF matchFirst). - - Delegating the kind of operation - to "g" flag is soon to be phased out along with the - ability to choose the exact matching scheme. The choice of - matching scheme to use depends highly on the pattern kind and - can done automatically on case by case basis. - - Returns: a $(D RegexMatch) object holding engine state after first match. -+/ - -public auto match(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) -{ - return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, re); -} - -///ditto -public auto match(R, String)(R input, String re) - if(isSomeString!R && isSomeString!String) -{ - return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, regex(re)); -} - -public auto match(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) -{ - return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re); -} - -/++ - Find the first (leftmost) slice of the $(D input) that - matches the pattern $(D re). This function picks the most suitable - regular expression engine depending on the pattern properties. - - $(D re) parameter can be one of three types: - $(UL - $(LI Plain string, in which case it's compiled to bytecode before matching. ) - $(LI Regex!char (wchar/dchar) that contains a pattern in the form of - compiled bytecode. ) - $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of - compiled native machine code. ) - ) - - Returns: - $(LREF Captures) containing the extent of a match together with all submatches - if there was a match, otherwise an empty $(LREF Captures) object. -+/ -public auto matchFirst(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) -{ - return matchOnce!ThompsonMatcher(input, re); -} - -///ditto -public auto matchFirst(R, String)(R input, String re) - if(isSomeString!R && isSomeString!String) -{ - return matchOnce!ThompsonMatcher(input, regex(re)); -} - -public auto matchFirst(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) -{ - return matchOnce!(BacktrackingMatcher!true)(input, re); -} - -/++ - Initiate a search for all non-overlapping matches to the pattern $(D re) - in the given $(D input). The result is a lazy range of matches generated - as they are encountered in the input going left to right. - - This function picks the most suitable regular expression engine - depending on the pattern properties. - - $(D re) parameter can be one of three types: - $(UL - $(LI Plain string, in which case it's compiled to bytecode before matching. ) - $(LI Regex!char (wchar/dchar) that contains a pattern in the form of - compiled bytecode. ) - $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of - compiled native machine code. ) - ) - - Returns: - $(LREF RegexMatch) object that represents matcher state - after the first match was found or an empty one if not present. -+/ -public auto matchAll(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) -{ - return matchMany!ThompsonMatcher(input, re); -} - -///ditto -public auto matchAll(R, String)(R input, String re) - if(isSomeString!R && isSomeString!String) -{ - return matchMany!ThompsonMatcher(input, regex(re)); -} - -public auto matchAll(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) -{ - return matchMany!(BacktrackingMatcher!true)(input, re); -} - -// another set of tests just to cover the new API -@system unittest -{ - foreach(String; TypeTuple!(string, wstring, const(dchar)[])) - { - auto str1 = "blah-bleh".to!String(); - auto pat1 = "bl[ae]h".to!String(); - auto mf = matchFirst(str1, pat1); - assert(mf.equal(["blah".to!String()])); - auto mAll = matchAll(str1, pat1); - assert(mAll.equal!((a,b) => a.equal(b)) - ([["blah".to!String()], ["bleh".to!String()]])); - - auto str2 = "1/03/12 - 3/03/12".to!String(); - auto pat2 = regex(r"(\d+)/(\d+)/(\d+)".to!String()); - auto mf2 = matchFirst(str2, pat2); - assert(mf2.equal(["1/03/12", "1", "03", "12"].map!(to!String)())); - auto mAll2 = matchAll(str2, pat2); - assert(mAll2.front.equal(mf2)); - mAll2.popFront(); - assert(mAll2.front.equal(["3/03/12", "3", "03", "12"].map!(to!String)())); - mf2.popFrontN(3); - assert(mf2.equal(["12".to!String()])); - - auto ctPat = ctRegex!(`(?P\d+)/(?P\d+)`.to!String()); - auto str = "2 + 34/56 - 6/1".to!String(); - auto cmf = matchFirst(str, ctPat); - assert(cmf.equal(["34/56", "34", "56"].map!(to!String)())); - assert(cmf["Quot"] == "34".to!String()); - assert(cmf["Denom"] == "56".to!String()); - - auto cmAll = matchAll(str, ctPat); - assert(cmAll.front.equal(cmf)); - cmAll.popFront(); - assert(cmAll.front.equal(["6/1", "6", "1"].map!(to!String)())); - } -} - -/++ - Start matching of $(D input) to regex pattern $(D re), - using traditional $(LUCKY backtracking) matching scheme. - - The use of this function is $(RED discouraged) - use either of - $(LREF matchAll) or $(LREF matchFirst). - - Delegating the kind of operation - to "g" flag is soon to be phased out along with the - ability to choose the exact matching scheme. The choice of - matching scheme to use depends highly on the pattern kind and - can done automatically on case by case basis. - - Returns: a $(D RegexMatch) object holding engine - state after first match. - -+/ -public auto bmatch(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) -{ - return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, re); -} - -///ditto -public auto bmatch(R, String)(R input, String re) - if(isSomeString!R && isSomeString!String) -{ - return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, regex(re)); -} - -public auto bmatch(R, RegEx)(R input, RegEx re) - if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) -{ - return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re); -} - - -enum isReplaceFunctor(alias fun, R) = - __traits(compiles, (Captures!R c) { fun(c); }); - -// the lowest level - just stuff replacements into the sink -private @trusted void replaceCapturesInto(alias output, Sink, R, T) - (ref Sink sink, R input, T captures) - if(isOutputRange!(Sink, dchar) && isSomeString!R) -{ - sink.put(captures.pre); - // a hack to get around bogus errors, should be simply output(captures, sink) - // "is a nested function and cannot be accessed from" - static if(isReplaceFunctor!(output, R)) - sink.put(output(captures)); //"mutator" type of function - else - output(captures, sink); //"output" type of function - sink.put(captures.post); -} - -// ditto for a range of captures -private void replaceMatchesInto(alias output, Sink, R, T) - (ref Sink sink, R input, T matches) - if(isOutputRange!(Sink, dchar) && isSomeString!R) -{ - size_t offset = 0; - foreach(cap; matches) - { - sink.put(cap.pre[offset .. $]); - // same hack, see replaceCapturesInto - static if(isReplaceFunctor!(output, R)) - sink.put(output(cap)); //"mutator" type of function - else - output(cap, sink); //"output" type of function - offset = cap.pre.length + cap.hit.length; - } - sink.put(input[offset .. $]); -} - -// a general skeleton of replaceFirst -private R replaceFirstWith(alias output, R, RegEx)(R input, RegEx re) - if(isSomeString!R && isRegexFor!(RegEx, R)) -{ - auto data = matchFirst(input, re); - if(data.empty) - return input; - auto app = appender!(R)(); - replaceCapturesInto!output(app, input, data); - return app.data; -} - -// ditto for replaceAll -// the method parameter allows old API to ride on the back of the new one -private R replaceAllWith(alias output, - alias method=matchAll, R, RegEx)(R input, RegEx re) - if(isSomeString!R && isRegexFor!(RegEx, R)) -{ - auto matches = method(input, re); //inout(C)[] fails - if(matches.empty) - return input; - auto app = appender!(R)(); - replaceMatchesInto!output(app, input, matches); - return app.data; -} - -/++ - Construct a new string from $(D input) by replacing the first match with - a string generated from it according to the $(D format) specifier. - - To replace all matches use $(LREF replaceAll). - - Params: - input = string to search - re = compiled regular expression to use - format = format string to generate replacements from, - see $(S_LINK Replace format string, the format string). - - Returns: - A string of the same type with the first match (if any) replaced. - If no match is found returns the input string itself. - - Example: - --- - assert(replaceFirst("noon", regex("n"), "[$&]") == "[n]oon"); - --- -+/ -public R replaceFirst(R, C, RegEx)(R input, RegEx re, const(C)[] format) - if(isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) -{ - return replaceFirstWith!((m, sink) => replaceFmt(format, m, sink))(input, re); -} - -/++ - This is a general replacement tool that construct a new string by replacing - matches of pattern $(D re) in the $(D input). Unlike the other overload - there is no format string instead captures are passed to - to a user-defined functor $(D fun) that returns a new string - to use as replacement. - - This version replaces the first match in $(D input), - see $(LREF replaceAll) to replace the all of the matches. - - Returns: - A new string of the same type as $(D input) with all matches - replaced by return values of $(D fun). If no matches found - returns the $(D input) itself. - - Example: - --- - string list = "#21 out of 46"; - string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1)) - (list, regex(`[0-9]+`)); - assert(newList == "#22 out of 46"); - --- -+/ -public R replaceFirst(alias fun, R, RegEx)(R input, RegEx re) - if(isSomeString!R && isRegexFor!(RegEx, R)) -{ - return replaceFirstWith!((m, sink) => sink.put(fun(m)))(input, re); -} - -/++ - A variation on $(LREF replaceFirst) that instead of allocating a new string - on each call outputs the result piece-wise to the $(D sink). In particular - this enables efficient construction of a final output incrementally. - - Like in $(LREF replaceFirst) family of functions there is an overload - for the substitution guided by the $(D format) string - and the one with the user defined callback. - - Example: - --- - import std.array; - string m1 = "first message\n"; - string m2 = "second message\n"; - auto result = appender!string(); - replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1"); - //equivalent of the above with user-defined callback - replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`)); - assert(result.data == "first\nsecond\n"); - --- -+/ -public @trusted void replaceFirstInto(Sink, R, C, RegEx) - (ref Sink sink, R input, RegEx re, const(C)[] format) - if(isOutputRange!(Sink, dchar) && isSomeString!R - && is(C : dchar) && isRegexFor!(RegEx, R)) - { - replaceCapturesInto!((m, sink) => replaceFmt(format, m, sink)) - (sink, input, matchFirst(input, re)); - } - -///ditto -public @trusted void replaceFirstInto(alias fun, Sink, R, RegEx) - (Sink sink, R input, RegEx re) - if(isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) -{ - replaceCapturesInto!fun(sink, input, matchFirst(input, re)); -} - -//examples for replaceFirst -@system unittest -{ - string list = "#21 out of 46"; - string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1)) - (list, regex(`[0-9]+`)); - assert(newList == "#22 out of 46"); - import std.array; - string m1 = "first message\n"; - string m2 = "second message\n"; - auto result = appender!string(); - replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1"); - //equivalent of the above with user-defined callback - replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`)); - assert(result.data == "first\nsecond\n"); -} - -/++ - Construct a new string from $(D input) by replacing all of the - fragments that match a pattern $(D re) with a string generated - from the match according to the $(D format) specifier. - - To replace only the first match use $(LREF replaceFirst). - - Params: - input = string to search - re = compiled regular expression to use - format = format string to generate replacements from, - see $(S_LINK Replace format string, the format string). - - Returns: - A string of the same type as $(D input) with the all - of the matches (if any) replaced. - If no match is found returns the input string itself. - - Example: - --- - // Comify a number - auto com = regex(r"(?<=\d)(?=(\d\d\d)+\b)","g"); - assert(replaceAll("12000 + 42100 = 54100", com, ",") == "12,000 + 42,100 = 54,100"); - --- -+/ -public @trusted R replaceAll(R, C, RegEx)(R input, RegEx re, const(C)[] format) - if(isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) -{ - return replaceAllWith!((m, sink) => replaceFmt(format, m, sink))(input, re); -} - -/++ - This is a general replacement tool that construct a new string by replacing - matches of pattern $(D re) in the $(D input). Unlike the other overload - there is no format string instead captures are passed to - to a user-defined functor $(D fun) that returns a new string - to use as replacement. - - This version replaces all of the matches found in $(D input), - see $(LREF replaceFirst) to replace the first match only. - - Returns: - A new string of the same type as $(D input) with all matches - replaced by return values of $(D fun). If no matches found - returns the $(D input) itself. - - Params: - input = string to search - re = compiled regular expression - fun = delegate to use - - Example: - Capitalize the letters 'a' and 'r': - --- - string baz(Captures!(string) m) - { - return std.string.toUpper(m.hit); - } - auto s = replaceAll!(baz)("Strap a rocket engine on a chicken.", - regex("[ar]")); - assert(s == "StRAp A Rocket engine on A chicken."); - --- -+/ -public @trusted R replaceAll(alias fun, R, RegEx)(R input, RegEx re) - if(isSomeString!R && isRegexFor!(RegEx, R)) -{ - return replaceAllWith!((m, sink) => sink.put(fun(m)))(input, re); -} - -/++ - A variation on $(LREF replaceAll) that instead of allocating a new string - on each call outputs the result piece-wise to the $(D sink). In particular - this enables efficient construction of a final output incrementally. - - As with $(LREF replaceAll) there are 2 overloads - one with a format string, - the other one with a user defined functor. - - Example: - --- - //swap all 3 letter words and bring it back - string text = "How are you doing?"; - auto sink = appender!(char[])(); - replaceAllInto!(cap => retro(cap[0]))(sink, text, regex(`\b\w{3}\b`)); - auto swapped = sink.data.dup; // make a copy explicitly - assert(swapped == "woH era uoy doing?"); - sink.clear(); - replaceAllInto!(cap => retro(cap[0]))(sink, swapped, regex(`\b\w{3}\b`)); - assert(sink.data == text); - --- -+/ -public @trusted void replaceAllInto(Sink, R, C, RegEx) - (Sink sink, R input, RegEx re, const(C)[] format) - if(isOutputRange!(Sink, dchar) && isSomeString!R - && is(C : dchar) && isRegexFor!(RegEx, R)) - { - replaceMatchesInto!((m, sink) => replaceFmt(format, m, sink)) - (sink, input, matchAll(input, re)); - } - -///ditto -public @trusted void replaceAllInto(alias fun, Sink, R, RegEx) - (Sink sink, R input, RegEx re) - if(isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) -{ - replaceMatchesInto!fun(sink, input, matchAll(input, re)); -} - -// a bit of examples -@system unittest -{ - //swap all 3 letter words and bring it back - string text = "How are you doing?"; - auto sink = appender!(char[])(); - replaceAllInto!(cap => retro(cap[0]))(sink, text, regex(`\b\w{3}\b`)); - auto swapped = sink.data.dup; // make a copy explicitly - assert(swapped == "woH era uoy doing?"); - sink.clear(); - replaceAllInto!(cap => retro(cap[0]))(sink, swapped, regex(`\b\w{3}\b`)); - assert(sink.data == text); -} - -// exercise all of the replace APIs -@system unittest -{ - // try and check first/all simple substitution - foreach(S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[])) - { - S s1 = "curt trial".to!S(); - S s2 = "round dome".to!S(); - S t1F = "court trial".to!S(); - S t2F = "hound dome".to!S(); - S t1A = "court trial".to!S(); - S t2A = "hound home".to!S(); - auto re1 = regex("curt".to!S()); - auto re2 = regex("[dr]o".to!S()); - - assert(replaceFirst(s1, re1, "court") == t1F); - assert(replaceFirst(s2, re2, "ho") == t2F); - assert(replaceAll(s1, re1, "court") == t1A); - assert(replaceAll(s2, re2, "ho") == t2A); - - auto rep1 = replaceFirst!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1); - assert(rep1 == t1F); - assert(replaceFirst!(cap => "ho".to!S())(s2, re2) == t2F); - auto rep1A = replaceAll!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1); - assert(rep1A == t1A); - assert(replaceAll!(cap => "ho".to!S())(s2, re2) == t2A); - - auto sink = appender!S(); - replaceFirstInto(sink, s1, re1, "court"); - assert(sink.data == t1F); - replaceFirstInto(sink, s2, re2, "ho"); - assert(sink.data == t1F~t2F); - replaceAllInto(sink, s1, re1, "court"); - assert(sink.data == t1F~t2F~t1A); - replaceAllInto(sink, s2, re2, "ho"); - assert(sink.data == t1F~t2F~t1A~t2A); - } -} - -/++ - Old API for replacement, operation depends on flags of pattern $(D re). - With "g" flag it performs the equivalent of $(LREF replaceAll) otherwise it - works the same as $(LREF replaceFirst). - - The use of this function is $(RED discouraged), please use $(LREF replaceAll) - or $(LREF replaceFirst) explicitly. -+/ -public R replace(alias scheme = match, R, C, RegEx)(R input, RegEx re, const(C)[] format) - if(isSomeString!R && isRegexFor!(RegEx, R)) -{ - return replaceAllWith!((m, sink) => replaceFmt(format, m, sink), match)(input, re); -} - -///ditto -public R replace(alias fun, R, RegEx)(R input, RegEx re) - if(isSomeString!R && isRegexFor!(RegEx, R)) -{ - return replaceAllWith!(fun, match)(input, re); -} - -//produce replacement string from format using captures for substitution -private void replaceFmt(R, Capt, OutR) - (R format, Capt captures, OutR sink, bool ignoreBadSubs = false) - if(isOutputRange!(OutR, ElementEncodingType!R[]) && - isOutputRange!(OutR, ElementEncodingType!(Capt.String)[])) -{ - enum State { Normal, Dollar } - auto state = State.Normal; - size_t offset; -L_Replace_Loop: - while(!format.empty) - final switch(state) - { - case State.Normal: - for(offset = 0; offset < format.length; offset++)//no decoding - { - if(format[offset] == '$') - { - state = State.Dollar; - sink.put(format[0 .. offset]); - format = format[offset+1 .. $];//ditto - continue L_Replace_Loop; - } - } - sink.put(format[0 .. offset]); - format = format[offset .. $]; - break; - case State.Dollar: - if(std.ascii.isDigit(format[0])) - { - uint digit = parse!uint(format); - enforce(ignoreBadSubs || digit < captures.length, text("invalid submatch number ", digit)); - if(digit < captures.length) - sink.put(captures[digit]); - } - else if(format[0] == '{') - { - auto x = find!(a => !std.ascii.isAlpha(a))(format[1..$]); - enforce(!x.empty && x[0] == '}', "no matching '}' in replacement format"); - auto name = format[1 .. $ - x.length]; - format = x[1..$]; - enforce(!name.empty, "invalid name in ${...} replacement format"); - sink.put(captures[name]); - } - else if(format[0] == '&') - { - sink.put(captures[0]); - format = format[1 .. $]; - } - else if(format[0] == '`') - { - sink.put(captures.pre); - format = format[1 .. $]; - } - else if(format[0] == '\'') - { - sink.put(captures.post); - format = format[1 .. $]; - } - else if(format[0] == '$') - { - sink.put(format[0 .. 1]); - format = format[1 .. $]; - } - state = State.Normal; - break; - } - enforce(state == State.Normal, "invalid format string in regex replace"); -} - -/++ -Range that splits a string using a regular expression as a -separator. - -Example: ----- -auto s1 = ", abc, de, fg, hi, "; -assert(equal(splitter(s1, regex(", *")), - ["", "abc", "de", "fg", "hi", ""])); ----- -+/ -public struct Splitter(Range, alias RegEx = Regex) - if(isSomeString!Range && isRegexFor!(RegEx, Range)) -{ -private: - Range _input; - size_t _offset; - alias Rx = typeof(match(Range.init,RegEx.init)); - Rx _match; - - @trusted this(Range input, RegEx separator) - {//@@@BUG@@@ generated opAssign of RegexMatch is not @trusted - _input = input; - separator.flags |= RegexOption.global; - if (_input.empty) - { - //there is nothing to match at all, make _offset > 0 - _offset = 1; - } - else - { - _match = Rx(_input, separator); - } - } - -public: - auto ref opSlice() - { - return this.save; - } - - ///Forward range primitives. - @property Range front() - { - assert(!empty && _offset <= _match.pre.length - && _match.pre.length <= _input.length); - return _input[_offset .. min($, _match.pre.length)]; - } - - ///ditto - @property bool empty() - { - return _offset > _input.length; - } - - ///ditto - void popFront() - { - assert(!empty); - if (_match.empty) - { - //No more separators, work is done here - _offset = _input.length + 1; - } - else - { - //skip past the separator - _offset = _match.pre.length + _match.hit.length; - _match.popFront(); - } - } - - ///ditto - @property auto save() - { - return this; - } -} - -/** - A helper function, creates a $(D Splitter) on range $(D r) separated by regex $(D pat). - Captured subexpressions have no effect on the resulting range. -*/ -public Splitter!(Range, RegEx) splitter(Range, RegEx)(Range r, RegEx pat) - if(is(BasicElementOf!Range : dchar) && isRegexFor!(RegEx, Range)) -{ - return Splitter!(Range, RegEx)(r, pat); -} - -///An eager version of $(D splitter) that creates an array with splitted slices of $(D input). -public @trusted String[] split(String, RegEx)(String input, RegEx rx) - if(isSomeString!String && isRegexFor!(RegEx, String)) -{ - auto a = appender!(String[])(); - foreach(e; splitter(input, rx)) - a.put(e); - return a.data; -} - -///Exception object thrown in case of errors during regex compilation. -public class RegexException : Exception -{ - /// - @trusted this(string msg, string file = __FILE__, size_t line = __LINE__) - {//@@@BUG@@@ Exception constructor is not @safe - super(msg, file, line); - } -} - -//--------------------- TEST SUITE --------------------------------- -version(unittest) -{ -@system: - -unittest -{//sanity checks - regex("(a|b)*"); - regex(`(?:([0-9A-F]+)\.\.([0-9A-F]+)|([0-9A-F]+))\s*;\s*(.*)\s*#`); - regex("abc|edf|ighrg"); - auto r1 = regex("abc"); - auto r2 = regex("(gylba)"); - assert(match("abcdef", r1).hit == "abc"); - assert(!match("wida",r2)); - assert(bmatch("abcdef", r1).hit == "abc"); - assert(!bmatch("wida", r2)); - assert(match("abc", "abc".dup)); - assert(bmatch("abc", "abc".dup)); - Regex!char rc; - assert(rc.empty); - rc = regex("test"); - assert(!rc.empty); -} - -/* The test vectors in this file are altered from Henry Spencer's regexp - test code. His copyright notice is: - - Copyright (c) 1986 by University of Toronto. - Written by Henry Spencer. Not derived from licensed software. - - Permission is granted to anyone to use this software for any - purpose on any computer system, and to redistribute it freely, - subject to the following restrictions: - - 1. The author is not responsible for the consequences of use of - this software, no matter how awful, even if they arise - from defects in it. - - 2. The origin of this software must not be misrepresented, either - by explicit claim or by omission. - - 3. Altered versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - - */ - -unittest -{ - struct TestVectors - { - string pattern; - string input; - string result; - string format; - string replace; - string flags; - } - - enum TestVectors[] tv = [ - TestVectors( "a\\b", "a", "y", "$&", "a" ), - TestVectors( "(a)b\\1", "abaab","y", "$&", "aba" ), - TestVectors( "()b\\1", "aaab", "y", "$&", "b" ), - TestVectors( "abc", "abc", "y", "$&", "abc" ), - TestVectors( "abc", "xbc", "n", "-", "-" ), - TestVectors( "abc", "axc", "n", "-", "-" ), - TestVectors( "abc", "abx", "n", "-", "-" ), - TestVectors( "abc", "xabcy","y", "$&", "abc" ), - TestVectors( "abc", "ababc","y", "$&", "abc" ), - TestVectors( "ab*c", "abc", "y", "$&", "abc" ), - TestVectors( "ab*bc", "abc", "y", "$&", "abc" ), - TestVectors( "ab*bc", "abbc", "y", "$&", "abbc" ), - TestVectors( "ab*bc", "abbbbc","y", "$&", "abbbbc" ), - TestVectors( "ab+bc", "abbc", "y", "$&", "abbc" ), - TestVectors( "ab+bc", "abc", "n", "-", "-" ), - TestVectors( "ab+bc", "abq", "n", "-", "-" ), - TestVectors( "ab+bc", "abbbbc","y", "$&", "abbbbc" ), - TestVectors( "ab?bc", "abbc", "y", "$&", "abbc" ), - TestVectors( "ab?bc", "abc", "y", "$&", "abc" ), - TestVectors( "ab?bc", "abbbbc","n", "-", "-" ), - TestVectors( "ab?c", "abc", "y", "$&", "abc" ), - TestVectors( "^abc$", "abc", "y", "$&", "abc" ), - TestVectors( "^abc$", "abcc", "n", "-", "-" ), - TestVectors( "^abc", "abcc", "y", "$&", "abc" ), - TestVectors( "^abc$", "aabc", "n", "-", "-" ), - TestVectors( "abc$", "aabc", "y", "$&", "abc" ), - TestVectors( "^", "abc", "y", "$&", "" ), - TestVectors( "$", "abc", "y", "$&", "" ), - TestVectors( "a.c", "abc", "y", "$&", "abc" ), - TestVectors( "a.c", "axc", "y", "$&", "axc" ), - TestVectors( "a.*c", "axyzc","y", "$&", "axyzc" ), - TestVectors( "a.*c", "axyzd","n", "-", "-" ), - TestVectors( "a[bc]d", "abc", "n", "-", "-" ), - TestVectors( "a[bc]d", "abd", "y", "$&", "abd" ), - TestVectors( "a[b-d]e", "abd", "n", "-", "-" ), - TestVectors( "a[b-d]e", "ace", "y", "$&", "ace" ), - TestVectors( "a[b-d]", "aac", "y", "$&", "ac" ), - TestVectors( "a[-b]", "a-", "y", "$&", "a-" ), - TestVectors( "a[b-]", "a-", "y", "$&", "a-" ), - TestVectors( "a[b-a]", "-", "c", "-", "-" ), - TestVectors( "a[]b", "-", "c", "-", "-" ), - TestVectors( "a[", "-", "c", "-", "-" ), - TestVectors( "a]", "a]", "y", "$&", "a]" ), - TestVectors( "a[\\]]b", "a]b", "y", "$&", "a]b" ), - TestVectors( "a[^bc]d", "aed", "y", "$&", "aed" ), - TestVectors( "a[^bc]d", "abd", "n", "-", "-" ), - TestVectors( "a[^-b]c", "adc", "y", "$&", "adc" ), - TestVectors( "a[^-b]c", "a-c", "n", "-", "-" ), - TestVectors( "a[^\\]b]c", "adc", "y", "$&", "adc" ), - TestVectors( "ab|cd", "abc", "y", "$&", "ab" ), - TestVectors( "ab|cd", "abcd", "y", "$&", "ab" ), - TestVectors( "()ef", "def", "y", "$&-$1", "ef-" ), - TestVectors( "()*", "-", "y", "-", "-" ), - TestVectors( "*a", "-", "c", "-", "-" ), - TestVectors( "^*", "-", "y", "-", "-" ), - TestVectors( "$*", "-", "y", "-", "-" ), - TestVectors( "(*)b", "-", "c", "-", "-" ), - TestVectors( "$b", "b", "n", "-", "-" ), - TestVectors( "a\\", "-", "c", "-", "-" ), - TestVectors( "a\\(b", "a(b", "y", "$&-$1", "a(b-" ), - TestVectors( "a\\(*b", "ab", "y", "$&", "ab" ), - TestVectors( "a\\(*b", "a((b", "y", "$&", "a((b" ), - TestVectors( "a\\\\b", "a\\b", "y", "$&", "a\\b" ), - TestVectors( "abc)", "-", "c", "-", "-" ), - TestVectors( "(abc", "-", "c", "-", "-" ), - TestVectors( "((a))", "abc", "y", "$&-$1-$2", "a-a-a" ), - TestVectors( "(a)b(c)", "abc", "y", "$&-$1-$2", "abc-a-c" ), - TestVectors( "a+b+c", "aabbabc","y", "$&", "abc" ), - TestVectors( "a**", "-", "c", "-", "-" ), - TestVectors( "a*?a", "aa", "y", "$&", "a" ), - TestVectors( "(a*)*", "aaa", "y", "-", "-" ), - TestVectors( "(a*)+", "aaa", "y", "-", "-" ), - TestVectors( "(a|)*", "-", "y", "-", "-" ), - TestVectors( "(a*|b)*", "aabb", "y", "-", "-" ), - TestVectors( "(a|b)*", "ab", "y", "$&-$1", "ab-b" ), - TestVectors( "(a+|b)*", "ab", "y", "$&-$1", "ab-b" ), - TestVectors( "(a+|b)+", "ab", "y", "$&-$1", "ab-b" ), - TestVectors( "(a+|b)?", "ab", "y", "$&-$1", "a-a" ), - TestVectors( "[^ab]*", "cde", "y", "$&", "cde" ), - TestVectors( "(^)*", "-", "y", "-", "-" ), - TestVectors( "(ab|)*", "-", "y", "-", "-" ), - TestVectors( ")(", "-", "c", "-", "-" ), - TestVectors( "", "abc", "y", "$&", "" ), - TestVectors( "abc", "", "n", "-", "-" ), - TestVectors( "a*", "", "y", "$&", "" ), - TestVectors( "([abc])*d", "abbbcd", "y", "$&-$1", "abbbcd-c" ), - TestVectors( "([abc])*bcd", "abcd", "y", "$&-$1", "abcd-a" ), - TestVectors( "a|b|c|d|e", "e", "y", "$&", "e" ), - TestVectors( "(a|b|c|d|e)f", "ef", "y", "$&-$1", "ef-e" ), - TestVectors( "((a*|b))*", "aabb", "y", "-", "-" ), - TestVectors( "abcd*efg", "abcdefg", "y", "$&", "abcdefg" ), - TestVectors( "ab*", "xabyabbbz", "y", "$&", "ab" ), - TestVectors( "ab*", "xayabbbz", "y", "$&", "a" ), - TestVectors( "(ab|cd)e", "abcde", "y", "$&-$1", "cde-cd" ), - TestVectors( "[abhgefdc]ij", "hij", "y", "$&", "hij" ), - TestVectors( "^(ab|cd)e", "abcde", "n", "x$1y", "xy" ), - TestVectors( "(abc|)ef", "abcdef", "y", "$&-$1", "ef-" ), - TestVectors( "(a|b)c*d", "abcd", "y", "$&-$1", "bcd-b" ), - TestVectors( "(ab|ab*)bc", "abc", "y", "$&-$1", "abc-a" ), - TestVectors( "a([bc]*)c*", "abc", "y", "$&-$1", "abc-bc" ), - TestVectors( "a([bc]*)(c*d)", "abcd", "y", "$&-$1-$2", "abcd-bc-d" ), - TestVectors( "a([bc]+)(c*d)", "abcd", "y", "$&-$1-$2", "abcd-bc-d" ), - TestVectors( "a([bc]*)(c+d)", "abcd", "y", "$&-$1-$2", "abcd-b-cd" ), - TestVectors( "a[bcd]*dcdcde", "adcdcde", "y", "$&", "adcdcde" ), - TestVectors( "a[bcd]+dcdcde", "adcdcde", "n", "-", "-" ), - TestVectors( "(ab|a)b*c", "abc", "y", "$&-$1", "abc-ab" ), - TestVectors( "((a)(b)c)(d)", "abcd", "y", "$1-$2-$3-$4", "abc-a-b-d" ), - TestVectors( "[a-zA-Z_][a-zA-Z0-9_]*", "alpha", "y", "$&", "alpha" ), - TestVectors( "^a(bc+|b[eh])g|.h$", "abh", "y", "$&-$1", "bh-" ), - TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "effgz", "y", "$&-$1-$2", "effgz-effgz-" ), - TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "ij", "y", "$&-$1-$2", "ij-ij-j" ), - TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "effg", "n", "-", "-" ), - TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "bcdd", "n", "-", "-" ), - TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "reffgz", "y", "$&-$1-$2", "effgz-effgz-" ), - TestVectors( "(((((((((a)))))))))", "a", "y", "$&", "a" ), - TestVectors( "multiple words of text", "uh-uh", "n", "-", "-" ), - TestVectors( "multiple words", "multiple words, yeah", "y", "$&", "multiple words" ), - TestVectors( "(.*)c(.*)", "abcde", "y", "$&-$1-$2", "abcde-ab-de" ), - TestVectors( "\\((.*), (.*)\\)", "(a, b)", "y", "($2, $1)", "(b, a)" ), - TestVectors( "abcd", "abcd", "y", "$&-&-$$$&", "abcd-&-$abcd" ), - TestVectors( "a(bc)d", "abcd", "y", "$1-$$1-$$$1", "bc-$1-$bc" ), - TestVectors( "[k]", "ab", "n", "-", "-" ), - TestVectors( "[ -~]*", "abc", "y", "$&", "abc" ), - TestVectors( "[ -~ -~]*", "abc", "y", "$&", "abc" ), - TestVectors( "[ -~ -~ -~]*", "abc", "y", "$&", "abc" ), - TestVectors( "[ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), - TestVectors( "[ -~ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), - TestVectors( "[ -~ -~ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), - TestVectors( "[ -~ -~ -~ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), - TestVectors( "a{2}", "candy", "n", "", "" ), - TestVectors( "a{2}", "caandy", "y", "$&", "aa" ), - TestVectors( "a{2}", "caaandy", "y", "$&", "aa" ), - TestVectors( "a{2,}", "candy", "n", "", "" ), - TestVectors( "a{2,}", "caandy", "y", "$&", "aa" ), - TestVectors( "a{2,}", "caaaaaandy", "y", "$&", "aaaaaa" ), - TestVectors( "a{1,3}", "cndy", "n", "", "" ), - TestVectors( "a{1,3}", "candy", "y", "$&", "a" ), - TestVectors( "a{1,3}", "caandy", "y", "$&", "aa" ), - TestVectors( "a{1,3}", "caaaaaandy", "y", "$&", "aaa" ), - TestVectors( "e?le?", "angel", "y", "$&", "el" ), - TestVectors( "e?le?", "angle", "y", "$&", "le" ), - TestVectors( "\\bn\\w", "noonday", "y", "$&", "no" ), - TestVectors( "\\wy\\b", "possibly yesterday", "y", "$&", "ly" ), - TestVectors( "\\w\\Bn", "noonday", "y", "$&", "on" ), - TestVectors( "y\\B\\w", "possibly yesterday", "y", "$&", "ye" ), - TestVectors( "\\cJ", "abc\ndef", "y", "$&", "\n" ), - TestVectors( "\\d", "B2 is", "y", "$&", "2" ), - TestVectors( "\\D", "B2 is", "y", "$&", "B" ), - TestVectors( "\\s\\w*", "foo bar", "y", "$&", " bar" ), - TestVectors( "\\S\\w*", "foo bar", "y", "$&", "foo" ), - TestVectors( "abc", "ababc", "y", "$&", "abc" ), - TestVectors( "apple(,)\\sorange\\1", "apple, orange, cherry, peach", "y", "$&", "apple, orange," ), - TestVectors( "(\\w+)\\s(\\w+)", "John Smith", "y", "$2, $1", "Smith, John" ), - TestVectors( "\\n\\f\\r\\t\\v", "abc\n\f\r\t\vdef", "y", "$&", "\n\f\r\t\v" ), - TestVectors( ".*c", "abcde", "y", "$&", "abc" ), - TestVectors( "^\\w+((;|=)\\w+)+$", "some=host=tld", "y", "$&-$1-$2", "some=host=tld-=tld-=" ), - TestVectors( "^\\w+((\\.|-)\\w+)+$", "some.host.tld", "y", "$&-$1-$2", "some.host.tld-.tld-." ), - TestVectors( "q(a|b)*q", "xxqababqyy", "y", "$&-$1", "qababq-b" ), - TestVectors( "^(a)(b){0,1}(c*)", "abcc", "y", "$1 $2 $3", "a b cc" ), - TestVectors( "^(a)((b){0,1})(c*)", "abcc", "y", "$1 $2 $3", "a b b" ), - TestVectors( "^(a)(b)?(c*)", "abcc", "y", "$1 $2 $3", "a b cc" ), - TestVectors( "^(a)((b)?)(c*)", "abcc", "y", "$1 $2 $3", "a b b" ), - TestVectors( "^(a)(b){0,1}(c*)", "acc", "y", "$1 $2 $3", "a cc" ), - TestVectors( "^(a)((b){0,1})(c*)", "acc", "y", "$1 $2 $3", "a " ), - TestVectors( "^(a)(b)?(c*)", "acc", "y", "$1 $2 $3", "a cc" ), - TestVectors( "^(a)((b)?)(c*)", "acc", "y", "$1 $2 $3", "a " ), - TestVectors( "(?:ab){3}", "_abababc","y", "$&-$1", "ababab-" ), - TestVectors( "(?:a(?:x)?)+", "aaxaxx", "y", "$&-$1-$2", "aaxax--" ), - TestVectors( `\W\w\W`, "aa b!ca", "y", "$&", " b!"), -//more repetitions: - TestVectors( "(?:a{2,4}b{1,3}){1,2}", "aaabaaaabbb", "y", "$&", "aaabaaaabbb" ), - TestVectors( "(?:a{2,4}b{1,3}){1,2}?", "aaabaaaabbb", "y", "$&", "aaab" ), -//groups: - TestVectors( "(abc)|(edf)|(xyz)", "xyz", "y", "$1-$2-$3","--xyz"), - TestVectors( "(?P\\d+)/(?P\\d+)", "2/3", "y", "${d}/${q}", "3/2"), -//set operations: - TestVectors( "[a-z--d-f]", " dfa", "y", "$&", "a"), - TestVectors( "[abc[pq--acq]]{2}", "bqpaca", "y", "$&", "pa"), - TestVectors( "[a-z9&&abc0-9]{3}", "z90a0abc", "y", "$&", "abc"), - TestVectors( "[0-9a-f~~0-5a-z]{2}", "g0a58x", "y", "$&", "8x"), - TestVectors( "[abc[pq]xyz[rs]]{4}", "cqxr", "y", "$&", "cqxr"), - TestVectors( "[abcdf--[ab&&[bcd]][acd]]", "abcdefgh", "y", "$&", "f"), -//unicode blocks & properties: - TestVectors( `\P{Inlatin1suppl ement}`, "\u00c2!", "y", "$&", "!"), - TestVectors( `\p{InLatin-1 Supplement}\p{in-mathematical-operators}\P{Inlatin1suppl ement}`, "\u00c2\u2200\u00c3\u2203.", "y", "$&", "\u00c3\u2203."), - TestVectors( `[-+*/\p{in-mathematical-operators}]{2}`, "a+\u2212", "y", "$&", "+\u2212"), - TestVectors( `\p{Ll}+`, "XabcD", "y", "$&", "abc"), - TestVectors( `\p{Lu}+`, "абвГДЕ", "y", "$&", "ГДЕ"), - TestVectors( `^\p{Currency Symbol}\p{Sc}`, "$₤", "y", "$&", "$₤"), - TestVectors( `\p{Common}\p{Thai}`, "!ฆ", "y", "$&", "!ฆ"), - TestVectors( `[\d\s]*\D`, "12 \t3\U00001680\u0F20_2", "y", "$&", "12 \t3\U00001680\u0F20_"), - TestVectors( `[c-wф]фф`, "ффф", "y", "$&", "ффф"), -//case insensitive: - TestVectors( `^abcdEf$`, "AbCdEF", "y", "$&", "AbCdEF", "i"), - TestVectors( `Русский язык`, "рУсскИй ЯзЫк", "y", "$&", "рУсскИй ЯзЫк", "i"), - TestVectors( `ⒶⒷⓒ` , "ⓐⓑⒸ", "y", "$&", "ⓐⓑⒸ", "i"), - TestVectors( "\U00010400{2}", "\U00010428\U00010400 ", "y", "$&", "\U00010428\U00010400", "i"), - TestVectors( `[adzУ-Я]{4}`, "DzюЯ", "y", "$&", "DzюЯ", "i"), - TestVectors( `\p{L}\p{Lu}{10}`, "абвгдеЖЗИКЛ", "y", "$&", "абвгдеЖЗИКЛ", "i"), - TestVectors( `(?:Dåb){3}`, "DåbDÅBdÅb", "y", "$&", "DåbDÅBdÅb", "i"), -//escapes: - TestVectors( `\u0041\u005a\U00000065\u0001`, "AZe\u0001", "y", "$&", "AZe\u0001"), - TestVectors( `\u`, "", "c", "-", "-"), - TestVectors( `\U`, "", "c", "-", "-"), - TestVectors( `\u003`, "", "c", "-", "-"), - TestVectors( `[\x00-\x7f]{4}`, "\x00\x09ab", "y", "$&", "\x00\x09ab"), - TestVectors( `[\cJ\cK\cA-\cD]{3}\cQ`, "\x01\x0B\x0A\x11", "y", "$&", "\x01\x0B\x0A\x11"), - TestVectors( `\r\n\v\t\f\\`, "\r\n\v\t\f\\", "y", "$&", "\r\n\v\t\f\\"), - TestVectors( `[\u0003\u0001]{2}`, "\u0001\u0003", "y", "$&", "\u0001\u0003"), - TestVectors( `^[\u0020-\u0080\u0001\n-\r]{8}`, "abc\u0001\v\f\r\n", "y", "$&", "abc\u0001\v\f\r\n"), - TestVectors( `\w+\S\w+`, "ab7!44c", "y", "$&", "ab7!44c"), - TestVectors( `\b\w+\b`, " abde4 ", "y", "$&", "abde4"), - TestVectors( `\b\w+\b`, " abde4", "y", "$&", "abde4"), - TestVectors( `\b\w+\b`, "abde4 ", "y", "$&", "abde4"), - TestVectors( `\pL\pS`, "a\u02DA", "y", "$&", "a\u02DA"), - TestVectors( `\pX`, "", "c", "-", "-"), -// ^, $, \b, \B, multiline : - TestVectors( `\r.*?$`, "abc\r\nxy", "y", "$&", "\r\nxy", "sm"), - TestVectors( `^a$^b$`, "a\r\nb\n", "n", "$&", "-", "m"), - TestVectors( `^a$\r\n^b$`,"a\r\nb\n", "y", "$&", "a\r\nb", "m"), - TestVectors( `^$`, "\r\n", "y", "$&", "", "m"), - TestVectors( `^a$\nx$`, "a\nx\u2028","y", "$&", "a\nx", "m"), - TestVectors( `^a$\nx$`, "a\nx\u2029","y", "$&", "a\nx", "m"), - TestVectors( `^a$\nx$`, "a\nx\u0085","y", "$&", "a\nx","m"), - TestVectors( `^x$`, "\u2028x", "y", "$&", "x", "m"), - TestVectors( `^x$`, "\u2029x", "y", "$&", "x", "m"), - TestVectors( `^x$`, "\u0085x", "y", "$&", "x", "m"), - TestVectors( `\b^.`, "ab", "y", "$&", "a"), - TestVectors( `\B^.`, "ab", "n", "-", "-"), - TestVectors( `^ab\Bc\B`, "\r\nabcd", "y", "$&", "abc", "m"), - TestVectors( `^.*$`, "12345678", "y", "$&", "12345678"), - -// luckily obtained regression on incremental matching in backtracker - TestVectors( `^(?:(?:([0-9A-F]+)\.\.([0-9A-F]+)|([0-9A-F]+))\s*;\s*([^ ]*)\s*#|# (?:\w|_)+=((?:\w|_)+))`, - "0020 ; White_Space # ", "y", "$1-$2-$3", "--0020"), -//lookahead - TestVectors( "(foo.)(?=(bar))", "foobar foodbar", "y", "$&-$1-$2", "food-food-bar" ), - TestVectors( `\b(\d+)[a-z](?=\1)`, "123a123", "y", "$&-$1", "123a-123" ), - TestVectors( `\$(?!\d{3})\w+`, "$123 $abc", "y", "$&", "$abc"), - TestVectors( `(abc)(?=(ed(f))\3)`, "abcedff", "y", "-", "-"), - TestVectors( `\b[A-Za-z0-9.]+(?=(@(?!gmail)))`, "a@gmail,x@com", "y", "$&-$1", "x-@"), - TestVectors( `x()(abc)(?=(d)(e)(f)\2)`, "xabcdefabc", "y", "$&", "xabc"), - TestVectors( `x()(abc)(?=(d)(e)(f)()\3\4\5)`, "xabcdefdef", "y", "$&", "xabc"), -//lookback - TestVectors( `(?<=(ab))\d`, "12ba3ab4", "y", "$&-$1", "4-ab", "i"), - TestVectors( `\w(?"); - assert(bmatch("texttext", greed).hit - == "text"); - auto cr8 = ctRegex!("^(a)(b)?(c*)"); - auto m8 = bmatch("abcc",cr8); - assert(m8); - assert(m8.captures[1] == "a"); - assert(m8.captures[2] == "b"); - assert(m8.captures[3] == "cc"); - auto cr9 = ctRegex!("q(a|b)*q"); - auto m9 = match("xxqababqyy",cr9); - assert(m9); - assert(equal(bmatch("xxqababqyy",cr9).captures, ["qababq", "b"])); - - auto rtr = regex("a|b|c"); - enum ctr = regex("a|b|c"); - assert(equal(rtr.ir,ctr.ir)); - //CTFE parser BUG is triggered by group - //in the middle of alternation (at least not first and not last) - enum testCT = regex(`abc|(edf)|xyz`); - auto testRT = regex(`abc|(edf)|xyz`); - assert(equal(testCT.ir,testRT.ir)); -} - -unittest -{ - enum cx = ctRegex!"(A|B|C)"; - auto mx = match("B",cx); - assert(mx); - assert(equal(mx.captures, [ "B", "B"])); - enum cx2 = ctRegex!"(A|B)*"; - assert(match("BAAA",cx2)); - - enum cx3 = ctRegex!("a{3,4}","i"); - auto mx3 = match("AaA",cx3); - assert(mx3); - assert(mx3.captures[0] == "AaA"); - enum cx4 = ctRegex!(`^a{3,4}?[a-zA-Z0-9~]{1,2}`,"i"); - auto mx4 = match("aaaabc", cx4); - assert(mx4); - assert(mx4.captures[0] == "aaaab"); - auto cr8 = ctRegex!("(a)(b)?(c*)"); - auto m8 = bmatch("abcc",cr8); - assert(m8); - assert(m8.captures[1] == "a"); - assert(m8.captures[2] == "b"); - assert(m8.captures[3] == "cc"); - auto cr9 = ctRegex!(".*$", "gm"); - auto m9 = match("First\rSecond", cr9); - assert(m9); - assert(equal(map!"a.hit"(m9), ["First", "", "Second"])); -} - -unittest -{ -//global matching - void test_body(alias matchFn)() - { - string s = "a quick brown fox jumps over a lazy dog"; - auto r1 = regex("\\b[a-z]+\\b","g"); - string[] test; - foreach(m; matchFn(s, r1)) - test ~= m.hit; - assert(equal(test, [ "a", "quick", "brown", "fox", "jumps", "over", "a", "lazy", "dog"])); - auto free_reg = regex(` - - abc - \s+ - " - ( - [^"]+ - | \\ " - )+ - " - z - `, "x"); - auto m = match(`abc "quoted string with \" inside"z`,free_reg); - assert(m); - string mails = " hey@you.com no@spam.net "; - auto rm = regex(`@(?<=\S+@)\S+`,"g"); - assert(equal(map!"a[0]"(matchFn(mails, rm)), ["@you.com", "@spam.net"])); - auto m2 = matchFn("First line\nSecond line",regex(".*$","gm")); - assert(equal(map!"a[0]"(m2), ["First line", "", "Second line"])); - auto m2a = matchFn("First line\nSecond line",regex(".+$","gm")); - assert(equal(map!"a[0]"(m2a), ["First line", "Second line"])); - auto m2b = matchFn("First line\nSecond line",regex(".+?$","gm")); - assert(equal(map!"a[0]"(m2b), ["First line", "Second line"])); - debug(std_regex_test) writeln("!!! FReD FLAGS test done "~matchFn.stringof~" !!!"); - } - test_body!bmatch(); - test_body!match(); -} - -//tests for accumulated std.regex issues and other regressions -unittest -{ - void test_body(alias matchFn)() - { - //issue 5857 - //matching goes out of control if ... in (...){x} has .*/.+ - auto c = matchFn("axxxzayyyyyzd",regex("(a.*z){2}d")).captures; - assert(c[0] == "axxxzayyyyyzd"); - assert(c[1] == "ayyyyyz"); - auto c2 = matchFn("axxxayyyyyd",regex("(a.*){2}d")).captures; - assert(c2[0] == "axxxayyyyyd"); - assert(c2[1] == "ayyyyy"); - //issue 2108 - //greedy vs non-greedy - auto nogreed = regex(""); - assert(matchFn("texttext", nogreed).hit - == "text"); - auto greed = regex(""); - assert(matchFn("texttext", greed).hit - == "texttext"); - //issue 4574 - //empty successful match still advances the input - string[] pres, posts, hits; - foreach(m; matchFn("abcabc", regex("","g"))) { - pres ~= m.pre; - posts ~= m.post; - assert(m.hit.empty); - - } - auto heads = [ - "abcabc", - "abcab", - "abca", - "abc", - "ab", - "a", - "" - ]; - auto tails = [ - "abcabc", - "bcabc", - "cabc", - "abc", - "bc", - "c", - "" - ]; - assert(pres == array(retro(heads))); - assert(posts == tails); - //issue 6076 - //regression on .* - auto re = regex("c.*|d"); - auto m = matchFn("mm", re); - assert(!m); - debug(std_regex_test) writeln("!!! FReD REGRESSION test done "~matchFn.stringof~" !!!"); - auto rprealloc = regex(`((.){5}.{1,10}){5}`); - auto arr = array(repeat('0',100)); - auto m2 = matchFn(arr, rprealloc); - assert(m2); - assert(collectException( - regex(r"^(import|file|binary|config)\s+([^\(]+)\(?([^\)]*)\)?\s*$") - ) is null); - foreach(ch; [Escapables]) - { - assert(match(to!string(ch),regex(`[\`~ch~`]`))); - assert(!match(to!string(ch),regex(`[^\`~ch~`]`))); - assert(match(to!string(ch),regex(`[\`~ch~`-\`~ch~`]`))); - } - //bugzilla 7718 - string strcmd = "./myApp.rb -os OSX -path \"/GIT/Ruby Apps/sec\" -conf 'notimer'"; - auto reStrCmd = regex (`(".*")|('.*')`, "g"); - assert(equal(map!"a[0]"(matchFn(strcmd, reStrCmd)), - [`"/GIT/Ruby Apps/sec"`, `'notimer'`])); - } - test_body!bmatch(); - test_body!match(); -} - -// tests for replace -unittest -{ - void test(alias matchFn)() - { - import std.string : toUpper; - - foreach(i, v; TypeTuple!(string, wstring, dstring)) - { - auto baz(Cap)(Cap m) - if (is(Cap == Captures!(Cap.String))) - { - return std.string.toUpper(m.hit); - } - alias String = v; - assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r")), to!String("c")) - == to!String("ack rapacity")); - assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r"), "g"), to!String("c")) - == to!String("ack capacity")); - assert(std.regex.replace!(matchFn)(to!String("noon"), regex(to!String("^n")), to!String("[$&]")) - == to!String("[n]oon")); - assert(std.regex.replace!(matchFn)(to!String("test1 test2"), regex(to!String(`\w+`),"g"), to!String("$`:$'")) - == to!String(": test2 test1 :")); - auto s = std.regex.replace!(baz!(Captures!(String)))(to!String("Strap a rocket engine on a chicken."), - regex(to!String("[ar]"), "g")); - assert(s == "StRAp A Rocket engine on A chicken."); - } - debug(std_regex_test) writeln("!!! Replace test done "~matchFn.stringof~" !!!"); - } - test!(bmatch)(); - test!(match)(); -} - -// tests for splitter -unittest -{ - auto s1 = ", abc, de, fg, hi, "; - auto sp1 = splitter(s1, regex(", *")); - auto w1 = ["", "abc", "de", "fg", "hi", ""]; - assert(equal(sp1, w1)); - - auto s2 = ", abc, de, fg, hi"; - auto sp2 = splitter(s2, regex(", *")); - auto w2 = ["", "abc", "de", "fg", "hi"]; - - uint cnt; - foreach(e; sp2) { - assert(w2[cnt++] == e); - } - assert(equal(sp2, w2)); -} - -unittest -{ - char[] s1 = ", abc, de, fg, hi, ".dup; - auto sp2 = splitter(s1, regex(", *")); -} - -unittest -{ - auto s1 = ", abc, de, fg, hi, "; - auto w1 = ["", "abc", "de", "fg", "hi", ""]; - assert(equal(split(s1, regex(", *")), w1[])); -} - -unittest -{ // bugzilla 7141 - string pattern = `[a\--b]`; - assert(match("-", pattern)); - assert(match("b", pattern)); - string pattern2 = `[&-z]`; - assert(match("b", pattern2)); -} -unittest -{//bugzilla 7111 - assert(match("", regex("^"))); -} -unittest -{//bugzilla 7300 - assert(!match("a"d, "aa"d)); -} - -unittest -{//bugzilla 7674 - assert("1234".replace(regex("^"), "$$") == "$1234"); - assert("hello?".replace(regex(r"\?", "g"), r"\?") == r"hello\?"); - assert("hello?".replace(regex(r"\?", "g"), r"\\?") != r"hello\?"); -} -unittest -{// bugzilla 7679 - foreach(S; TypeTuple!(string, wstring, dstring)) - { - enum re = ctRegex!(to!S(r"\.")); - auto str = to!S("a.b"); - assert(equal(std.regex.splitter(str, re), [to!S("a"), to!S("b")])); - assert(split(str, re) == [to!S("a"), to!S("b")]); - } -} -unittest -{//bugzilla 8203 - string data = " - NAME = XPAW01_STA:STATION - NAME = XPAW01_STA - "; - auto uniFileOld = data; - auto r = regex( - r"^NAME = (?P[a-zA-Z0-9_]+):*(?P[a-zA-Z0-9_]*)","gm"); - auto uniCapturesNew = match(uniFileOld, r); - for(int i = 0; i < 20; i++) - foreach (matchNew; uniCapturesNew) {} - //a second issue with same symptoms - auto r2 = regex(`([а-яА-Я\-_]+\s*)+(?<=[\s\.,\^])`); - match("аллея Театральная", r2); -} -unittest -{// bugzilla 8637 purity of enforce - auto m = match("hello world", regex("world")); - enforce(m); -} - -// bugzilla 8725 -unittest -{ - static italic = regex( r"\* - (?!\s+) - (.*?) - (?!\s+) - \*", "gx" ); - string input = "this * is* interesting, *very* interesting"; - assert(replace(input, italic, "$1") == - "this * is* interesting, very interesting"); -} - -// bugzilla 8349 -unittest -{ - enum peakRegexStr = r"\>(wgEncode.*Tfbs.*\.(?:narrow)|(?:broad)Peak.gz)"; - enum peakRegex = ctRegex!(peakRegexStr); - //note that the regex pattern itself is probably bogus - assert(match(r"\>wgEncode-blah-Tfbs.narrow", peakRegex)); -} - -// bugzilla 9211 -unittest -{ - auto rx_1 = regex(r"^(\w)*(\d)"); - auto m = match("1234", rx_1); - assert(equal(m.front, ["1234", "3", "4"])); - auto rx_2 = regex(r"^([0-9])*(\d)"); - auto m2 = match("1234", rx_2); - assert(equal(m2.front, ["1234", "3", "4"])); -} - -// bugzilla 9280 -unittest -{ - string tomatch = "a!b@c"; - static r = regex(r"^(?P.*?)!(?P.*?)@(?P.*?)$"); - auto nm = match(tomatch, r); - assert(nm); - auto c = nm.captures; - assert(c[1] == "a"); - assert(c["nick"] == "a"); -} - - -// bugzilla 9579 -unittest -{ - char[] input = ['a', 'b', 'c']; - string format = "($1)"; - // used to give a compile error: - auto re = regex(`(a)`, "g"); - auto r = replace(input, re, format); - assert(r == "(a)bc"); -} - -// bugzilla 9634 -unittest -{ - auto re = ctRegex!"(?:a+)"; - assert(match("aaaa", re).hit == "aaaa"); -} - -//bugzilla 10798 -unittest -{ - auto cr = ctRegex!("[abcd--c]*"); - auto m = "abc".match(cr); - assert(m); - assert(m.hit == "ab"); -} - -// bugzilla 10913 -unittest -{ - @system static string foo(const(char)[] s) - { - return s.dup; - } - @safe static string bar(const(char)[] s) - { - return s.dup; - } - () @system { - replace!((a) => foo(a.hit))("blah", regex(`a`)); - }(); - () @safe { - replace!((a) => bar(a.hit))("blah", regex(`a`)); - }(); -} - -// bugzilla 11262 -unittest -{ - enum reg = ctRegex!(r",", "g"); - auto str = "This,List"; - str = str.replace(reg, "-"); - assert(str == "This-List"); -} - -// bugzilla 11775 -unittest -{ - assert(collectException(regex("a{1,0}"))); -} - -// bugzilla 11839 -unittest -{ - assert(regex(`(?P\w+)`).namedCaptures.equal(["var1"])); - assert(collectException(regex(`(?P<1>\w+)`))); - assert(regex(`(?P\w+)`).namedCaptures.equal(["v1"])); - assert(regex(`(?P<__>\w+)`).namedCaptures.equal(["__"])); - assert(regex(`(?P<я>\w+)`).namedCaptures.equal(["я"])); -} - -// bugzilla 12076 -unittest -{ - auto RE = ctRegex!(r"(?abc)`); - assert(collectException("abc".matchFirst(r)["b"])); -} - -// bugzilla 12691 -unittest -{ - assert(bmatch("e@", "^([a-z]|)*$").empty); - assert(bmatch("e@", ctRegex!`^([a-z]|)*$`).empty); -} - -//bugzilla 12713 -unittest -{ - assertThrown(regex("[[a-z]([a-z]|(([[a-z])))")); -} - -//bugzilla 12747 -unittest -{ - assertThrown(regex(`^x(\1)`)); - assertThrown(regex(`^(x(\1))`)); - assertThrown(regex(`^((x)(?=\1))`)); -} - -}//version(unittest) diff --git a/std/regex/internal/backtracking.d b/std/regex/internal/backtracking.d new file mode 100644 index 00000000000..9e2cd656e73 --- /dev/null +++ b/std/regex/internal/backtracking.d @@ -0,0 +1,1406 @@ +/* + Implementation of backtracking std.regex engine. + Contains both compile-time and run-time versions. +*/ +module std.regex.internal.backtracking; + +package(std.regex): + +import std.regex.internal.ir; +import std.range, std.typecons, std.traits, core.stdc.stdlib; + +/+ + BacktrackingMatcher implements backtracking scheme of matching + regular expressions. ++/ +template BacktrackingMatcher(bool CTregex) +{ + @trusted struct BacktrackingMatcher(Char, Stream = Input!Char) + if(is(Char : dchar)) + { + alias DataIndex = Stream.DataIndex; + struct State + {//top bit in pc is set if saved along with matches + DataIndex index; + uint pc, counter, infiniteNesting; + } + static assert(State.sizeof % size_t.sizeof == 0); + enum stateSize = State.sizeof / size_t.sizeof; + enum initialStack = 1<<11; // items in a block of segmented stack + alias const(Char)[] String; + alias RegEx = Regex!Char; + alias MatchFn = bool function (ref BacktrackingMatcher!(Char, Stream)); + RegEx re; //regex program + static if(CTregex) + MatchFn nativeFn; //native code for that program + //Stream state + Stream s; + DataIndex index; + dchar front; + bool exhausted; + //backtracking machine state + uint pc, counter; + DataIndex lastState = 0; //top of state stack + DataIndex[] trackers; + static if(!CTregex) + uint infiniteNesting; + size_t[] memory; + //local slice of matches, global for backref + Group!DataIndex[] matches, backrefed; + + static if(__traits(hasMember,Stream, "search")) + { + enum kicked = true; + } + else + enum kicked = false; + + static size_t initialMemory(const ref RegEx re) + { + return (re.ngroup+1)*DataIndex.sizeof //trackers + + stackSize(re)*size_t.sizeof; + } + + static size_t stackSize(const ref RegEx re) + { + return initialStack*(stateSize + re.ngroup*(Group!DataIndex).sizeof/size_t.sizeof)+1; + } + + @property bool atStart(){ return index == 0; } + + @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } + + void next() + { + if(!s.nextChar(front, index)) + index = s.lastIndex; + } + + void search() + { + static if(kicked) + { + if(!s.search(re.kickstart, front, index)) + { + index = s.lastIndex; + } + } + else + next(); + } + + // + void newStack() + { + auto chunk = mallocArray!(size_t)(stackSize(re)); + chunk[0] = cast(size_t)(memory.ptr); + memory = chunk[1..$]; + } + + void initExternalMemory(void[] memBlock) + { + trackers = arrayInChunk!(DataIndex)(re.ngroup+1, memBlock); + memory = cast(size_t[])memBlock; + memory[0] = 0; //hidden pointer + memory = memory[1..$]; + } + + void initialize(ref RegEx program, Stream stream, void[] memBlock) + { + re = program; + s = stream; + exhausted = false; + initExternalMemory(memBlock); + backrefed = null; + } + + auto dupTo(void[] memory) + { + typeof(this) tmp = this; + tmp.initExternalMemory(memory); + return tmp; + } + + this(ref RegEx program, Stream stream, void[] memBlock, dchar ch, DataIndex idx) + { + initialize(program, stream, memBlock); + front = ch; + index = idx; + } + + this(ref RegEx program, Stream stream, void[] memBlock) + { + initialize(program, stream, memBlock); + next(); + } + + auto fwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock) + { + alias BackMatcherTempl = .BacktrackingMatcher!(CTregex); + alias BackMatcher = BackMatcherTempl!(Char, Stream); + auto fwdMatcher = BackMatcher(matcher.re, s, memBlock, front, index); + return fwdMatcher; + } + + auto bwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock) + { + alias BackMatcherTempl = .BacktrackingMatcher!(CTregex); + alias BackMatcher = BackMatcherTempl!(Char, typeof(s.loopBack(index))); + auto fwdMatcher = + BackMatcher(matcher.re, s.loopBack(index), memBlock); + return fwdMatcher; + } + + // + bool matchFinalize() + { + size_t start = index; + if(matchImpl()) + {//stream is updated here + matches[0].begin = start; + matches[0].end = index; + if(!(re.flags & RegexOption.global) || atEnd) + exhausted = true; + if(start == index)//empty match advances input + next(); + return true; + } + else + return false; + } + + //lookup next match, fill matches with indices into input + bool match(Group!DataIndex[] matches) + { + debug(std_regex_matcher) + { + writeln("------------------------------------------"); + } + if(exhausted) //all matches collected + return false; + this.matches = matches; + if(re.flags & RegexInfo.oneShot) + { + exhausted = true; + DataIndex start = index; + auto m = matchImpl(); + if(m) + { + matches[0].begin = start; + matches[0].end = index; + } + return m; + } + static if(kicked) + { + if(!re.kickstart.empty) + { + for(;;) + { + + if(matchFinalize()) + return true; + else + { + if(atEnd) + break; + search(); + if(atEnd) + { + exhausted = true; + return matchFinalize(); + } + } + } + exhausted = true; + return false; //early return + } + } + //no search available - skip a char at a time + for(;;) + { + if(matchFinalize()) + return true; + else + { + if(atEnd) + break; + next(); + if(atEnd) + { + exhausted = true; + return matchFinalize(); + } + } + } + exhausted = true; + return false; + } + + /+ + match subexpression against input, + results are stored in matches + +/ + bool matchImpl() + { + static if(CTregex && is(typeof(nativeFn(this)))) + { + debug(std_regex_ctr) writeln("using C-T matcher"); + return nativeFn(this); + } + else + { + pc = 0; + counter = 0; + lastState = 0; + infiniteNesting = -1;//intentional + auto start = s._index; + debug(std_regex_matcher) + writeln("Try match starting at ", s[index..s.lastIndex]); + for(;;) + { + debug(std_regex_matcher) + writefln("PC: %s\tCNT: %s\t%s \tfront: %s src: %s", + pc, counter, disassemble(re.ir, pc, re.dict), + front, s._index); + switch(re.ir[pc].code) + { + case IR.OrChar://assumes IRL!(OrChar) == 1 + if(atEnd) + goto L_backtrack; + uint len = re.ir[pc].sequence; + uint end = pc + len; + if(re.ir[pc].data != front && re.ir[pc+1].data != front) + { + for(pc = pc+2; pc < end; pc++) + if(re.ir[pc].data == front) + break; + if(pc == end) + goto L_backtrack; + } + pc = end; + next(); + break; + case IR.Char: + if(atEnd || front != re.ir[pc].data) + goto L_backtrack; + pc += IRL!(IR.Char); + next(); + break; + case IR.Any: + if(atEnd || (!(re.flags & RegexOption.singleline) + && (front == '\r' || front == '\n'))) + goto L_backtrack; + pc += IRL!(IR.Any); + next(); + break; + case IR.CodepointSet: + if(atEnd || !re.charsets[re.ir[pc].data].scanFor(front)) + goto L_backtrack; + next(); + pc += IRL!(IR.CodepointSet); + break; + case IR.Trie: + if(atEnd || !re.tries[re.ir[pc].data][front]) + goto L_backtrack; + next(); + pc += IRL!(IR.Trie); + break; + case IR.Wordboundary: + dchar back; + DataIndex bi; + //at start & end of input + if(atStart && wordTrie[front]) + { + pc += IRL!(IR.Wordboundary); + break; + } + else if(atEnd && s.loopBack(index).nextChar(back, bi) + && wordTrie[back]) + { + pc += IRL!(IR.Wordboundary); + break; + } + else if(s.loopBack(index).nextChar(back, bi)) + { + bool af = wordTrie[front]; + bool ab = wordTrie[back]; + if(af ^ ab) + { + pc += IRL!(IR.Wordboundary); + break; + } + } + goto L_backtrack; + case IR.Notwordboundary: + dchar back; + DataIndex bi; + //at start & end of input + if(atStart && wordTrie[front]) + goto L_backtrack; + else if(atEnd && s.loopBack(index).nextChar(back, bi) + && wordTrie[back]) + goto L_backtrack; + else if(s.loopBack(index).nextChar(back, bi)) + { + bool af = wordTrie[front]; + bool ab = wordTrie[back]; + if(af ^ ab) + goto L_backtrack; + } + pc += IRL!(IR.Wordboundary); + break; + case IR.Bol: + dchar back; + DataIndex bi; + if(atStart) + pc += IRL!(IR.Bol); + else if((re.flags & RegexOption.multiline) + && s.loopBack(index).nextChar(back,bi) + && endOfLine(back, front == '\n')) + { + pc += IRL!(IR.Bol); + } + else + goto L_backtrack; + break; + case IR.Eol: + dchar back; + DataIndex bi; + debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]); + //no matching inside \r\n + if(atEnd || ((re.flags & RegexOption.multiline) + && endOfLine(front, s.loopBack(index).nextChar(back,bi) + && back == '\r'))) + { + pc += IRL!(IR.Eol); + } + else + goto L_backtrack; + break; + case IR.InfiniteStart, IR.InfiniteQStart: + trackers[infiniteNesting+1] = index; + pc += re.ir[pc].data + IRL!(IR.InfiniteStart); + //now pc is at end IR.Infininite(Q)End + uint len = re.ir[pc].data; + int test; + if(re.ir[pc].code == IR.InfiniteEnd) + { + test = quickTestFwd(pc+IRL!(IR.InfiniteEnd), front, re); + if(test >= 0) + pushState(pc+IRL!(IR.InfiniteEnd), counter); + infiniteNesting++; + pc -= len; + } + else + { + test = quickTestFwd(pc - len, front, re); + if(test >= 0) + { + infiniteNesting++; + pushState(pc - len, counter); + infiniteNesting--; + } + pc += IRL!(IR.InfiniteEnd); + } + break; + case IR.RepeatStart, IR.RepeatQStart: + pc += re.ir[pc].data + IRL!(IR.RepeatStart); + break; + case IR.RepeatEnd: + case IR.RepeatQEnd: + //len, step, min, max + uint len = re.ir[pc].data; + uint step = re.ir[pc+2].raw; + uint min = re.ir[pc+3].raw; + uint max = re.ir[pc+4].raw; + if(counter < min) + { + counter += step; + pc -= len; + } + else if(counter < max) + { + if(re.ir[pc].code == IR.RepeatEnd) + { + pushState(pc + IRL!(IR.RepeatEnd), counter%step); + counter += step; + pc -= len; + } + else + { + pushState(pc - len, counter + step); + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + } + else + { + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + break; + case IR.InfiniteEnd: + case IR.InfiniteQEnd: + uint len = re.ir[pc].data; + debug(std_regex_matcher) writeln("Infinited nesting:", infiniteNesting); + assert(infiniteNesting < trackers.length); + + if(trackers[infiniteNesting] == index) + {//source not consumed + pc += IRL!(IR.InfiniteEnd); + infiniteNesting--; + break; + } + else + trackers[infiniteNesting] = index; + int test; + if(re.ir[pc].code == IR.InfiniteEnd) + { + test = quickTestFwd(pc+IRL!(IR.InfiniteEnd), front, re); + if(test >= 0) + { + infiniteNesting--; + pushState(pc + IRL!(IR.InfiniteEnd), counter); + infiniteNesting++; + } + pc -= len; + } + else + { + test = quickTestFwd(pc-len, front, re); + if(test >= 0) + pushState(pc-len, counter); + pc += IRL!(IR.InfiniteEnd); + infiniteNesting--; + } + break; + case IR.OrEnd: + pc += IRL!(IR.OrEnd); + break; + case IR.OrStart: + pc += IRL!(IR.OrStart); + goto case; + case IR.Option: + uint len = re.ir[pc].data; + if(re.ir[pc+len].code == IR.GotoEndOr)//not a last one + { + pushState(pc + len + IRL!(IR.Option), counter); //remember 2nd branch + } + pc += IRL!(IR.Option); + break; + case IR.GotoEndOr: + pc = pc + re.ir[pc].data + IRL!(IR.GotoEndOr); + break; + case IR.GroupStart: + uint n = re.ir[pc].data; + matches[n].begin = index; + debug(std_regex_matcher) writefln("IR group #%u starts at %u", n, index); + pc += IRL!(IR.GroupStart); + break; + case IR.GroupEnd: + uint n = re.ir[pc].data; + matches[n].end = index; + debug(std_regex_matcher) writefln("IR group #%u ends at %u", n, index); + pc += IRL!(IR.GroupEnd); + break; + case IR.LookaheadStart: + case IR.NeglookaheadStart: + uint len = re.ir[pc].data; + auto save = index; + uint ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw; + auto mem = malloc(initialMemory(re))[0..initialMemory(re)]; + scope(exit) free(mem.ptr); + static if(Stream.isLoopback) + { + auto matcher = bwdMatcher(this, mem); + } + else + { + auto matcher = fwdMatcher(this, mem); + } + matcher.matches = matches[ms .. me]; + matcher.backrefed = backrefed.empty ? matches : backrefed; + matcher.re.ir = re.ir[pc+IRL!(IR.LookaheadStart) .. pc+IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd)]; + bool match = matcher.matchImpl() ^ (re.ir[pc].code == IR.NeglookaheadStart); + s.reset(save); + next(); + if(!match) + goto L_backtrack; + else + { + pc += IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd); + } + break; + case IR.LookbehindStart: + case IR.NeglookbehindStart: + uint len = re.ir[pc].data; + uint ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw; + auto mem = malloc(initialMemory(re))[0..initialMemory(re)]; + scope(exit) free(mem.ptr); + static if(Stream.isLoopback) + { + alias Matcher = BacktrackingMatcher!(Char, Stream); + auto matcher = Matcher(re, s, mem, front, index); + } + else + { + alias Matcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index))); + auto matcher = Matcher(re, s.loopBack(index), mem); + } + matcher.matches = matches[ms .. me]; + matcher.re.ir = re.ir[pc + IRL!(IR.LookbehindStart) .. pc + IRL!(IR.LookbehindStart) + len + IRL!(IR.LookbehindEnd)]; + matcher.backrefed = backrefed.empty ? matches : backrefed; + bool match = matcher.matchImpl() ^ (re.ir[pc].code == IR.NeglookbehindStart); + if(!match) + goto L_backtrack; + else + { + pc += IRL!(IR.LookbehindStart)+len+IRL!(IR.LookbehindEnd); + } + break; + case IR.Backref: + uint n = re.ir[pc].data; + auto referenced = re.ir[pc].localRef + ? s[matches[n].begin .. matches[n].end] + : s[backrefed[n].begin .. backrefed[n].end]; + while(!atEnd && !referenced.empty && front == referenced.front) + { + next(); + referenced.popFront(); + } + if(referenced.empty) + pc++; + else + goto L_backtrack; + break; + case IR.Nop: + pc += IRL!(IR.Nop); + break; + case IR.LookaheadEnd: + case IR.NeglookaheadEnd: + case IR.LookbehindEnd: + case IR.NeglookbehindEnd: + case IR.End: + return true; + default: + debug printBytecode(re.ir[0..$]); + assert(0); + L_backtrack: + if(!popState()) + { + s.reset(start); + return false; + } + } + } + } + assert(0); + } + + @property size_t stackAvail() + { + return memory.length - lastState; + } + + bool prevStack() + { + import core.stdc.stdlib; + size_t* prev = memory.ptr-1; + prev = cast(size_t*)*prev;//take out hidden pointer + if(!prev) + return false; + free(memory.ptr);//last segment is freed in RegexMatch + immutable size = initialStack*(stateSize + 2*re.ngroup); + memory = prev[0..size]; + lastState = size; + return true; + } + + void stackPush(T)(T val) + if(!isDynamicArray!T) + { + *cast(T*)&memory[lastState] = val; + enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof; + lastState += delta; + debug(std_regex_matcher) writeln("push element SP= ", lastState); + } + + void stackPush(T)(T[] val) + { + static assert(T.sizeof % size_t.sizeof == 0); + (cast(T*)&memory[lastState])[0..val.length] + = val[0..$]; + lastState += val.length*(T.sizeof/size_t.sizeof); + debug(std_regex_matcher) writeln("push array SP= ", lastState); + } + + void stackPop(T)(ref T val) + if(!isDynamicArray!T) + { + enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof; + lastState -= delta; + val = *cast(T*)&memory[lastState]; + debug(std_regex_matcher) writeln("pop element SP= ", lastState); + } + + void stackPop(T)(T[] val) + { + stackPop(val); // call ref version + } + void stackPop(T)(ref T[] val) + { + lastState -= val.length*(T.sizeof/size_t.sizeof); + val[0..$] = (cast(T*)&memory[lastState])[0..val.length]; + debug(std_regex_matcher) writeln("pop array SP= ", lastState); + } + + static if(!CTregex) + { + //helper function, saves engine state + void pushState(uint pc, uint counter) + { + if(stateSize + trackers.length + matches.length > stackAvail) + { + newStack(); + lastState = 0; + } + *cast(State*)&memory[lastState] = + State(index, pc, counter, infiniteNesting); + lastState += stateSize; + memory[lastState .. lastState + 2 * matches.length] = (cast(size_t[])matches)[]; + lastState += 2*matches.length; + if(trackers.length) + { + memory[lastState .. lastState + trackers.length] = trackers[]; + lastState += trackers.length; + } + debug(std_regex_matcher) + writefln("Saved(pc=%s) front: %s src: %s", + pc, front, s[index..s.lastIndex]); + } + + //helper function, restores engine state + bool popState() + { + if(!lastState) + return prevStack(); + if (trackers.length) + { + lastState -= trackers.length; + trackers[] = memory[lastState .. lastState + trackers.length]; + } + lastState -= 2*matches.length; + auto pm = cast(size_t[])matches; + pm[] = memory[lastState .. lastState + 2 * matches.length]; + lastState -= stateSize; + State* state = cast(State*)&memory[lastState]; + index = state.index; + pc = state.pc; + counter = state.counter; + infiniteNesting = state.infiniteNesting; + debug(std_regex_matcher) + { + writefln("Restored matches", front, s[index .. s.lastIndex]); + foreach(i, m; matches) + writefln("Sub(%d) : %s..%s", i, m.begin, m.end); + } + s.reset(index); + next(); + debug(std_regex_matcher) + writefln("Backtracked (pc=%s) front: %s src: %s", + pc, front, s[index..s.lastIndex]); + return true; + } + } + } +} + +//very shitty string formatter, $$ replaced with next argument converted to string +@trusted string ctSub( U...)(string format, U args) +{ + import std.conv; + bool seenDollar; + foreach(i, ch; format) + { + if(ch == '$') + { + if(seenDollar) + { + static if(args.length > 0) + { + return format[0 .. i - 1] ~ to!string(args[0]) + ~ ctSub(format[i + 1 .. $], args[1 .. $]); + } + else + assert(0); + } + else + seenDollar = true; + } + else + seenDollar = false; + + } + return format; +} + +alias Sequence(int B, int E) = staticIota!(B, E); + +struct CtContext +{ + import std.conv; + //dirty flags + bool counter, infNesting; + // to make a unique advancement counter per nesting level of loops + int curInfLoop, nInfLoops; + //to mark the portion of matches to save + int match, total_matches; + int reserved; + + + //state of codegenerator + struct CtState + { + string code; + int addr; + } + + this(Char)(Regex!Char re) + { + match = 1; + reserved = 1; //first match is skipped + total_matches = re.ngroup; + } + + CtContext lookaround(uint s, uint e) + { + CtContext ct; + ct.total_matches = e - s; + ct.match = 1; + return ct; + } + + //restore state having current context + string restoreCode() + { + string text; + //stack is checked in L_backtrack + text ~= counter + ? " + stackPop(counter);" + : " + counter = 0;"; + if(infNesting) + { + text ~= ctSub(` + stackPop(trackers[0..$$]); + `, curInfLoop + 1); + } + if(match < total_matches) + { + text ~= ctSub(" + stackPop(matches[$$..$$]);", reserved, match); + text ~= ctSub(" + matches[$$..$] = typeof(matches[0]).init;", match); + } + else + text ~= ctSub(" + stackPop(matches[$$..$]);", reserved); + return text; + } + + //save state having current context + string saveCode(uint pc, string count_expr="counter") + { + string text = ctSub(" + if(stackAvail < $$*(Group!(DataIndex)).sizeof/size_t.sizeof + trackers.length + $$) + { + newStack(); + lastState = 0; + }", match - reserved, cast(int)counter + 2); + if(match < total_matches) + text ~= ctSub(" + stackPush(matches[$$..$$]);", reserved, match); + else + text ~= ctSub(" + stackPush(matches[$$..$]);", reserved); + if(infNesting) + { + text ~= ctSub(` + stackPush(trackers[0..$$]); + `, curInfLoop + 1); + } + text ~= counter ? ctSub(" + stackPush($$);", count_expr) : ""; + text ~= ctSub(" + stackPush(index); stackPush($$); \n", pc); + return text; + } + + // + CtState ctGenBlock(Bytecode[] ir, int addr) + { + CtState result; + result.addr = addr; + while(!ir.empty) + { + auto n = ctGenGroup(ir, result.addr); + result.code ~= n.code; + result.addr = n.addr; + } + return result; + } + + // + CtState ctGenGroup(ref Bytecode[] ir, int addr) + { + import std.algorithm : max; + auto bailOut = "goto L_backtrack;"; + auto nextInstr = ctSub("goto case $$;", addr+1); + CtState r; + assert(!ir.empty); + switch(ir[0].code) + { + case IR.InfiniteStart, IR.InfiniteQStart, IR.RepeatStart, IR.RepeatQStart: + bool infLoop = + ir[0].code == IR.InfiniteStart || ir[0].code == IR.InfiniteQStart; + infNesting = infNesting || infLoop; + if(infLoop) + { + curInfLoop++; + nInfLoops = max(nInfLoops, curInfLoop+1); + } + counter = counter || + ir[0].code == IR.RepeatStart || ir[0].code == IR.RepeatQStart; + uint len = ir[0].data; + auto nir = ir[ir[0].length .. ir[0].length+len]; + r = ctGenBlock(nir, addr+1); + if(infLoop) + curInfLoop--; + //start/end codegen + //r.addr is at last test+ jump of loop, addr+1 is body of loop + nir = ir[ir[0].length + len .. $]; + r.code = ctGenFixupCode(ir[0..ir[0].length], addr, r.addr) ~ r.code; + r.code ~= ctGenFixupCode(nir, r.addr, addr+1); + r.addr += 2; //account end instruction + restore state + ir = nir; + break; + case IR.OrStart: + uint len = ir[0].data; + auto nir = ir[ir[0].length .. ir[0].length+len]; + r = ctGenAlternation(nir, addr); + ir = ir[ir[0].length + len .. $]; + assert(ir[0].code == IR.OrEnd); + ir = ir[ir[0].length..$]; + break; + case IR.LookaheadStart: + case IR.NeglookaheadStart: + case IR.LookbehindStart: + case IR.NeglookbehindStart: + uint len = ir[0].data; + bool behind = ir[0].code == IR.LookbehindStart || ir[0].code == IR.NeglookbehindStart; + bool negative = ir[0].code == IR.NeglookaheadStart || ir[0].code == IR.NeglookbehindStart; + string fwdType = "typeof(fwdMatcher(matcher, []))"; + string bwdType = "typeof(bwdMatcher(matcher, []))"; + string fwdCreate = "fwdMatcher(matcher, mem)"; + string bwdCreate = "bwdMatcher(matcher, mem)"; + uint start = IRL!(IR.LookbehindStart); + uint end = IRL!(IR.LookbehindStart)+len+IRL!(IR.LookaheadEnd); + CtContext context = lookaround(ir[1].raw, ir[2].raw); //split off new context + auto slice = ir[start .. end]; + r.code ~= ctSub(` + case $$: //fake lookaround "atom" + static if(typeof(matcher.s).isLoopback) + alias Lookaround = $$; + else + alias Lookaround = $$; + static bool matcher_$$(ref Lookaround matcher) @trusted + { + //(neg)lookaround piece start + $$ + //(neg)lookaround piece ends + } + auto save = index; + auto mem = malloc(initialMemory(re))[0..initialMemory(re)]; + scope(exit) free(mem.ptr); + static if(typeof(matcher.s).isLoopback) + auto lookaround = $$; + else + auto lookaround = $$; + lookaround.matches = matches[$$..$$]; + lookaround.backrefed = backrefed.empty ? matches : backrefed; + lookaround.nativeFn = &matcher_$$; //hookup closure's binary code + bool match = $$; + s.reset(save); + next(); + if(match) + $$ + else + $$`, addr, + behind ? fwdType : bwdType, behind ? bwdType : fwdType, + addr, context.ctGenRegEx(slice), + behind ? fwdCreate : bwdCreate, behind ? bwdCreate : fwdCreate, + ir[1].raw, ir[2].raw, //start - end of matches slice + addr, + negative ? "!lookaround.matchImpl()" : "lookaround.matchImpl()", + nextInstr, bailOut); + ir = ir[end .. $]; + r.addr = addr + 1; + break; + case IR.LookaheadEnd: case IR.NeglookaheadEnd: + case IR.LookbehindEnd: case IR.NeglookbehindEnd: + ir = ir[IRL!(IR.LookaheadEnd) .. $]; + r.addr = addr; + break; + default: + assert(ir[0].isAtom, text(ir[0].mnemonic)); + r = ctGenAtom(ir, addr); + } + return r; + } + + //generate source for bytecode contained in OrStart ... OrEnd + CtState ctGenAlternation(Bytecode[] ir, int addr) + { + CtState[] pieces; + CtState r; + enum optL = IRL!(IR.Option); + for(;;) + { + assert(ir[0].code == IR.Option); + auto len = ir[0].data; + if(optL+len < ir.length && ir[optL+len].code == IR.Option)//not a last option + { + auto nir = ir[optL .. optL+len-IRL!(IR.GotoEndOr)]; + r = ctGenBlock(nir, addr+2);//space for Option + restore state + //r.addr+1 to account GotoEndOr at end of branch + r.code = ctGenFixupCode(ir[0 .. ir[0].length], addr, r.addr+1) ~ r.code; + addr = r.addr+1;//leave space for GotoEndOr + pieces ~= r; + ir = ir[optL + len .. $]; + } + else + { + pieces ~= ctGenBlock(ir[optL..$], addr); + addr = pieces[$-1].addr; + break; + } + } + r = pieces[0]; + for(uint i = 1; i < pieces.length; i++) + { + r.code ~= ctSub(` + case $$: + goto case $$; `, pieces[i-1].addr, addr); + r.code ~= pieces[i].code; + } + r.addr = addr; + return r; + } + + // generate fixup code for instruction in ir, + // fixup means it has an alternative way for control flow + string ctGenFixupCode(Bytecode[] ir, int addr, int fixup) + { + return ctGenFixupCode(ir, addr, fixup); // call ref Bytecode[] version + } + string ctGenFixupCode(ref Bytecode[] ir, int addr, int fixup) + { + string r; + string testCode; + r = ctSub(` + case $$: debug(std_regex_matcher) writeln("#$$");`, + addr, addr); + switch(ir[0].code) + { + case IR.InfiniteStart, IR.InfiniteQStart: + r ~= ctSub( ` + trackers[$$] = DataIndex.max; + goto case $$;`, curInfLoop, fixup); + ir = ir[ir[0].length..$]; + break; + case IR.InfiniteEnd: + testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1); + r ~= ctSub( ` + if(trackers[$$] == index) + {//source not consumed + goto case $$; + } + trackers[$$] = index; + + $$ + { + $$ + } + goto case $$; + case $$: //restore state and go out of loop + $$ + goto case;`, curInfLoop, addr+2, + curInfLoop, testCode, saveCode(addr+1), + fixup, addr+1, restoreCode()); + ir = ir[ir[0].length..$]; + break; + case IR.InfiniteQEnd: + testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1); + auto altCode = testCode.length ? ctSub("else goto case $$;", fixup) : ""; + r ~= ctSub( ` + if(trackers[$$] == index) + {//source not consumed + goto case $$; + } + trackers[$$] = index; + + $$ + { + $$ + goto case $$; + } + $$ + case $$://restore state and go inside loop + $$ + goto case $$;`, curInfLoop, addr+2, + curInfLoop, testCode, saveCode(addr+1), + addr+2, altCode, addr+1, restoreCode(), fixup); + ir = ir[ir[0].length..$]; + break; + case IR.RepeatStart, IR.RepeatQStart: + r ~= ctSub( ` + goto case $$;`, fixup); + ir = ir[ir[0].length..$]; + break; + case IR.RepeatEnd, IR.RepeatQEnd: + //len, step, min, max + uint len = ir[0].data; + uint step = ir[2].raw; + uint min = ir[3].raw; + uint max = ir[4].raw; + r ~= ctSub(` + if(counter < $$) + { + debug(std_regex_matcher) writeln("RepeatEnd min case pc=", $$); + counter += $$; + goto case $$; + }`, min, addr, step, fixup); + if(ir[0].code == IR.RepeatEnd) + { + string counter_expr = ctSub("counter % $$", step); + r ~= ctSub(` + else if(counter < $$) + { + $$ + counter += $$; + goto case $$; + }`, max, saveCode(addr+1, counter_expr), step, fixup); + } + else + { + string counter_expr = ctSub("counter % $$", step); + r ~= ctSub(` + else if(counter < $$) + { + $$ + counter = counter % $$; + goto case $$; + }`, max, saveCode(addr+1,counter_expr), step, addr+2); + } + r ~= ctSub(` + else + { + counter = counter % $$; + goto case $$; + } + case $$: //restore state + $$ + goto case $$;`, step, addr+2, addr+1, restoreCode(), + ir[0].code == IR.RepeatEnd ? addr+2 : fixup ); + ir = ir[ir[0].length..$]; + break; + case IR.Option: + r ~= ctSub( ` + { + $$ + } + goto case $$; + case $$://restore thunk to go to the next group + $$ + goto case $$;`, saveCode(addr+1), addr+2, + addr+1, restoreCode(), fixup); + ir = ir[ir[0].length..$]; + break; + default: + assert(0, text(ir[0].mnemonic)); + } + return r; + } + + + string ctQuickTest(Bytecode[] ir, int id) + { + uint pc = 0; + while(pc < ir.length && ir[pc].isAtom) + { + if(ir[pc].code == IR.GroupStart || ir[pc].code == IR.GroupEnd) + { + pc++; + } + else if(ir[pc].code == IR.Backref) + break; + else + { + auto code = ctAtomCode(ir[pc..$], -1); + return ctSub(` + int test_$$() + { + $$ //$$ + } + if(test_$$() >= 0)`, id, code.ptr ? code : "return 0;", + ir[pc].mnemonic, id); + } + } + return ""; + } + + //process & generate source for simple bytecodes at front of ir using address addr + CtState ctGenAtom(ref Bytecode[] ir, int addr) + { + CtState result; + result.code = ctAtomCode(ir, addr); + ir.popFrontN(ir[0].code == IR.OrChar ? ir[0].sequence : ir[0].length); + result.addr = addr + 1; + return result; + } + + //D code for atom at ir using address addr, addr < 0 means quickTest + string ctAtomCode(Bytecode[] ir, int addr) + { + string code; + string bailOut, nextInstr; + if(addr < 0) + { + bailOut = "return -1;"; + nextInstr = "return 0;"; + } + else + { + bailOut = "goto L_backtrack;"; + nextInstr = ctSub("goto case $$;", addr+1); + code ~= ctSub( ` + case $$: debug(std_regex_matcher) writeln("#$$"); + `, addr, addr); + } + switch(ir[0].code) + { + case IR.OrChar://assumes IRL!(OrChar) == 1 + code ~= ctSub(` + if(atEnd) + $$`, bailOut); + uint len = ir[0].sequence; + for(uint i = 0; i < len; i++) + { + code ~= ctSub( ` + if(front == $$) + { + $$ + $$ + }`, ir[i].data, addr >= 0 ? "next();" :"", nextInstr); + } + code ~= ctSub( ` + $$`, bailOut); + break; + case IR.Char: + code ~= ctSub( ` + if(atEnd || front != $$) + $$ + $$ + $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); + break; + case IR.Any: + code ~= ctSub( ` + if(atEnd || (!(re.flags & RegexOption.singleline) + && (front == '\r' || front == '\n'))) + $$ + $$ + $$`, bailOut, addr >= 0 ? "next();" :"",nextInstr); + break; + case IR.CodepointSet: + code ~= ctSub( ` + if(atEnd || !re.charsets[$$].scanFor(front)) + $$ + $$ + $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); + break; + case IR.Trie: + code ~= ctSub( ` + if(atEnd || !re.tries[$$][front]) + $$ + $$ + $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); + break; + case IR.Wordboundary: + code ~= ctSub( ` + dchar back; + DataIndex bi; + if(atStart && wordTrie[front]) + { + $$ + } + else if(atEnd && s.loopBack(index).nextChar(back, bi) + && wordTrie[back]) + { + $$ + } + else if(s.loopBack(index).nextChar(back, bi)) + { + bool af = wordTrie[front]; + bool ab = wordTrie[back]; + if(af ^ ab) + { + $$ + } + } + $$`, nextInstr, nextInstr, nextInstr, bailOut); + break; + case IR.Notwordboundary: + code ~= ctSub( ` + dchar back; + DataIndex bi; + //at start & end of input + if(atStart && wordTrie[front]) + $$ + else if(atEnd && s.loopBack(index).nextChar(back, bi) + && wordTrie[back]) + $$ + else if(s.loopBack(index).nextChar(back, bi)) + { + bool af = wordTrie[front]; + bool ab = wordTrie[back]; + if(af ^ ab) + $$ + } + $$`, bailOut, bailOut, bailOut, nextInstr); + + break; + case IR.Bol: + code ~= ctSub(` + dchar back; + DataIndex bi; + if(atStart || ((re.flags & RegexOption.multiline) + && s.loopBack(index).nextChar(back,bi) + && endOfLine(back, front == '\n'))) + { + debug(std_regex_matcher) writeln("BOL matched"); + $$ + } + else + $$`, nextInstr, bailOut); + + break; + case IR.Eol: + code ~= ctSub(` + dchar back; + DataIndex bi; + debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]); + //no matching inside \r\n + if(atEnd || ((re.flags & RegexOption.multiline) + && endOfLine(front, s.loopBack(index).nextChar(back,bi) + && back == '\r'))) + { + debug(std_regex_matcher) writeln("EOL matched"); + $$ + } + else + $$`, nextInstr, bailOut); + + break; + case IR.GroupStart: + code ~= ctSub(` + matches[$$].begin = index; + $$`, ir[0].data, nextInstr); + match = ir[0].data+1; + break; + case IR.GroupEnd: + code ~= ctSub(` + matches[$$].end = index; + $$`, ir[0].data, nextInstr); + break; + case IR.Backref: + string mStr = "auto referenced = "; + mStr ~= ir[0].localRef + ? ctSub("s[matches[$$].begin .. matches[$$].end];", + ir[0].data, ir[0].data) + : ctSub("s[backrefed[$$].begin .. backrefed[$$].end];", + ir[0].data, ir[0].data); + code ~= ctSub( ` + $$ + while(!atEnd && !referenced.empty && front == referenced.front) + { + next(); + referenced.popFront(); + } + if(referenced.empty) + $$ + else + $$`, mStr, nextInstr, bailOut); + break; + case IR.Nop: + case IR.End: + break; + default: + assert(0, text(ir[0].mnemonic, " is not supported yet")); + } + return code; + } + + //generate D code for the whole regex + public string ctGenRegEx(Bytecode[] ir) + { + auto bdy = ctGenBlock(ir, 0); + auto r = ` + import core.stdc.stdlib; + with(matcher) + { + pc = 0; + counter = 0; + lastState = 0; + auto start = s._index;`; + r ~= ` + goto StartLoop; + debug(std_regex_matcher) writeln("Try CT matching starting at ",s[index..s.lastIndex]); + L_backtrack: + if(lastState || prevStack()) + { + stackPop(pc); + stackPop(index); + s.reset(index); + next(); + } + else + { + s.reset(start); + return false; + } + StartLoop: + switch(pc) + { + `; + r ~= bdy.code; + r ~= ctSub(` + case $$: break;`,bdy.addr); + r ~= ` + default: + assert(0); + } + return true; + } + `; + return r; + } + +} + +string ctGenRegExCode(Char)(Regex!Char re) +{ + auto context = CtContext(re); + return context.ctGenRegEx(re.ir); +} diff --git a/std/regex/internal/generator.d b/std/regex/internal/generator.d new file mode 100644 index 00000000000..541acc3c268 --- /dev/null +++ b/std/regex/internal/generator.d @@ -0,0 +1,185 @@ +/* + Generators - components that generate strings for a given regex pattern. + + For the moment undocumented, and is subject to change. +*/ +module std.regex.internal.generator; + +/* + Useful utility for self-testing, an infinite range of string samples + that _have_ to match given compiled regex. + Caveats: supports only a simple subset of bytecode. +*/ +@trusted private struct SampleGenerator(Char) +{ + import std.regex.internal.ir; + import std.array, std.format, std.utf, std.random; + Regex!Char re; + Appender!(char[]) app; + uint limit, seed; + Xorshift gen; + //generator for pattern r, with soft maximum of threshold elements + //and a given random seed + this(ref Regex!Char r, uint threshold, uint randomSeed) + { + re = r; + limit = threshold; + seed = randomSeed; + app = appender!(Char[])(); + compose(); + } + + uint rand(uint x) + { + uint r = gen.front % x; + gen.popFront(); + return r; + } + + void compose() + { + uint pc = 0, counter = 0, dataLenOld = uint.max; + for(;;) + { + switch(re.ir[pc].code) + { + case IR.Char: + formattedWrite(app,"%s", cast(dchar)re.ir[pc].data); + pc += IRL!(IR.Char); + break; + case IR.OrChar: + uint len = re.ir[pc].sequence; + formattedWrite(app, "%s", cast(dchar)re.ir[pc + rand(len)].data); + pc += len; + break; + case IR.CodepointSet: + case IR.Trie: + auto set = re.charsets[re.ir[pc].data]; + auto x = rand(cast(uint)set.byInterval.length); + auto y = rand(set.byInterval[x].b - set.byInterval[x].a); + formattedWrite(app, "%s", cast(dchar)(set.byInterval[x].a+y)); + pc += IRL!(IR.CodepointSet); + break; + case IR.Any: + uint x; + do + { + x = rand(0x11_000); + }while(x == '\r' || x == '\n' || !isValidDchar(x)); + formattedWrite(app, "%s", cast(dchar)x); + pc += IRL!(IR.Any); + break; + case IR.GotoEndOr: + pc += IRL!(IR.GotoEndOr)+re.ir[pc].data; + assert(re.ir[pc].code == IR.OrEnd); + goto case; + case IR.OrEnd: + pc += IRL!(IR.OrEnd); + break; + case IR.OrStart: + pc += IRL!(IR.OrStart); + goto case; + case IR.Option: + uint next = pc + re.ir[pc].data + IRL!(IR.Option); + uint nOpt = 0; + //queue next Option + while(re.ir[next].code == IR.Option) + { + nOpt++; + next += re.ir[next].data + IRL!(IR.Option); + } + nOpt++; + nOpt = rand(nOpt); + for(;nOpt; nOpt--) + { + pc += re.ir[pc].data + IRL!(IR.Option); + } + assert(re.ir[pc].code == IR.Option); + pc += IRL!(IR.Option); + break; + case IR.RepeatStart:case IR.RepeatQStart: + pc += IRL!(IR.RepeatStart)+re.ir[pc].data; + goto case IR.RepeatEnd; + case IR.RepeatEnd: + case IR.RepeatQEnd: + uint len = re.ir[pc].data; + uint step = re.ir[pc+2].raw; + uint min = re.ir[pc+3].raw; + if(counter < min) + { + counter += step; + pc -= len; + break; + } + uint max = re.ir[pc+4].raw; + if(counter < max) + { + if(app.data.length < limit && rand(3) > 0) + { + pc -= len; + counter += step; + } + else + { + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + } + else + { + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + break; + case IR.InfiniteStart, IR.InfiniteQStart: + pc += re.ir[pc].data + IRL!(IR.InfiniteStart); + goto case IR.InfiniteEnd; //both Q and non-Q + case IR.InfiniteEnd: + case IR.InfiniteQEnd: + uint len = re.ir[pc].data; + if(app.data.length == dataLenOld) + { + pc += IRL!(IR.InfiniteEnd); + break; + } + dataLenOld = cast(uint)app.data.length; + if(app.data.length < limit && rand(3) > 0) + pc = pc - len; + else + pc = pc + IRL!(IR.InfiniteEnd); + break; + case IR.GroupStart, IR.GroupEnd: + pc += IRL!(IR.GroupStart); + break; + case IR.Bol, IR.Wordboundary, IR.Notwordboundary: + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + default: + return; + } + } + } + + @property Char[] front() + { + return app.data; + } + + @property empty(){ return false; } + + void popFront() + { + app.shrinkTo(0); + compose(); + } +} + +unittest +{ + import std.range, std.regex; + auto re = regex(`P[a-z]{3,}q`); + auto gen = SampleGenerator!char(re, 20, 3141592); + static assert(isInputRange!(typeof(gen))); + //@@@BUG@@@ somehow gen.take(1_000) doesn't work + foreach(v; take(gen, 1_000)) + assert(v.match(re)); +} diff --git a/std/regex/internal/ir.d b/std/regex/internal/ir.d new file mode 100644 index 00000000000..f2172bd77b3 --- /dev/null +++ b/std/regex/internal/ir.d @@ -0,0 +1,749 @@ +/* + Implementation of std.regex IR, an intermediate representation + of a regular expression pattern. + + This is a common ground between frontend regex component (parser) + and backend components - generators, matchers and other "filters". +*/ +module std.regex.internal.ir; + +package(std.regex): + +import std.exception, std.uni, std.typetuple, std.traits, std.range; + +// just a common trait, may be moved elsewhere +alias BasicElementOf(Range) = Unqual!(ElementEncodingType!Range); + +// heuristic value determines maximum CodepointSet length suitable for linear search +enum maxCharsetUsed = 6; + +// another variable to tweak behavior of caching generated Tries for character classes +enum maxCachedTries = 8; + +alias CodepointSetTrie!(13, 8) Trie; +alias codepointSetTrie!(13, 8) makeTrie; + +Trie[CodepointSet] trieCache; + +//accessor with caching +@trusted Trie getTrie(CodepointSet set) +{// @@@BUG@@@ 6357 almost all properties of AA are not @safe + if(__ctfe || maxCachedTries == 0) + return makeTrie(set); + else + { + auto p = set in trieCache; + if(p) + return *p; + if(trieCache.length == maxCachedTries) + { + // flush entries in trieCache + trieCache = null; + } + return (trieCache[set] = makeTrie(set)); + } +} + +@trusted auto memoizeExpr(string expr)() +{ + if(__ctfe) + return mixin(expr); + alias T = typeof(mixin(expr)); + static T slot; + static bool initialized; + if(!initialized) + { + slot = mixin(expr); + initialized = true; + } + return slot; +} + +//property for \w character class +@property CodepointSet wordCharacter() +{ + return memoizeExpr!("unicode.Alphabetic | unicode.Mn | unicode.Mc + | unicode.Me | unicode.Nd | unicode.Pc")(); +} + +@property Trie wordTrie() +{ + return memoizeExpr!("makeTrie(wordCharacter)")(); +} + +// some special Unicode white space characters +private enum NEL = '\u0085', LS = '\u2028', PS = '\u2029'; + +//Regular expression engine/parser options: +// global - search all nonoverlapping matches in input +// casefold - case insensitive matching, do casefolding on match in unicode mode +// freeform - ignore whitespace in pattern, to match space use [ ] or \s +// multiline - switch ^, $ detect start and end of linesinstead of just start and end of input +enum RegexOption: uint { + global = 0x1, + casefold = 0x2, + freeform = 0x4, + nonunicode = 0x8, + multiline = 0x10, + singleline = 0x20 +} +//do not reorder this list +alias RegexOptionNames = TypeTuple!('g', 'i', 'x', 'U', 'm', 's'); +static assert( RegexOption.max < 0x80); +// flags that allow guide execution of engine +enum RegexInfo : uint { oneShot = 0x80 } + +// IR bit pattern: 0b1_xxxxx_yy +// where yy indicates class of instruction, xxxxx for actual operation code +// 00: atom, a normal instruction +// 01: open, opening of a group, has length of contained IR in the low bits +// 10: close, closing of a group, has length of contained IR in the low bits +// 11 unused +// +// Loops with Q (non-greedy, with ? mark) must have the same size / other properties as non Q version +// Possible changes: +//* merge group, option, infinite/repeat start (to never copy during parsing of (a|b){1,2}) +//* reorganize groups to make n args easier to find, or simplify the check for groups of similar ops +// (like lookaround), or make it easier to identify hotspots. + +enum IR:uint { + Char = 0b1_00000_00, //a character + Any = 0b1_00001_00, //any character + CodepointSet = 0b1_00010_00, //a most generic CodepointSet [...] + Trie = 0b1_00011_00, //CodepointSet implemented as Trie + //match with any of a consecutive OrChar's in this sequence + //(used for case insensitive match) + //OrChar holds in upper two bits of data total number of OrChars in this _sequence_ + //the drawback of this representation is that it is difficult + // to detect a jump in the middle of it + OrChar = 0b1_00100_00, + Nop = 0b1_00101_00, //no operation (padding) + End = 0b1_00110_00, //end of program + Bol = 0b1_00111_00, //beginning of a string ^ + Eol = 0b1_01000_00, //end of a string $ + Wordboundary = 0b1_01001_00, //boundary of a word + Notwordboundary = 0b1_01010_00, //not a word boundary + Backref = 0b1_01011_00, //backreference to a group (that has to be pinned, i.e. locally unique) (group index) + GroupStart = 0b1_01100_00, //start of a group (x) (groupIndex+groupPinning(1bit)) + GroupEnd = 0b1_01101_00, //end of a group (x) (groupIndex+groupPinning(1bit)) + Option = 0b1_01110_00, //start of an option within an alternation x | y (length) + GotoEndOr = 0b1_01111_00, //end of an option (length of the rest) + //... any additional atoms here + + OrStart = 0b1_00000_01, //start of alternation group (length) + OrEnd = 0b1_00000_10, //end of the or group (length,mergeIndex) + //with this instruction order + //bit mask 0b1_00001_00 could be used to test/set greediness + InfiniteStart = 0b1_00001_01, //start of an infinite repetition x* (length) + InfiniteEnd = 0b1_00001_10, //end of infinite repetition x* (length,mergeIndex) + InfiniteQStart = 0b1_00010_01, //start of a non eager infinite repetition x*? (length) + InfiniteQEnd = 0b1_00010_10, //end of non eager infinite repetition x*? (length,mergeIndex) + RepeatStart = 0b1_00011_01, //start of a {n,m} repetition (length) + RepeatEnd = 0b1_00011_10, //end of x{n,m} repetition (length,step,minRep,maxRep) + RepeatQStart = 0b1_00100_01, //start of a non eager x{n,m}? repetition (length) + RepeatQEnd = 0b1_00100_10, //end of non eager x{n,m}? repetition (length,step,minRep,maxRep) + // + LookaheadStart = 0b1_00101_01, //begin of the lookahead group (length) + LookaheadEnd = 0b1_00101_10, //end of a lookahead group (length) + NeglookaheadStart = 0b1_00110_01, //start of a negative lookahead (length) + NeglookaheadEnd = 0b1_00110_10, //end of a negative lookahead (length) + LookbehindStart = 0b1_00111_01, //start of a lookbehind (length) + LookbehindEnd = 0b1_00111_10, //end of a lookbehind (length) + NeglookbehindStart= 0b1_01000_01, //start of a negative lookbehind (length) + NeglookbehindEnd = 0b1_01000_10, //end of negative lookbehind (length) +} + +//a shorthand for IR length - full length of specific opcode evaluated at compile time +template IRL(IR code) +{ + enum uint IRL = lengthOfIR(code); +} +static assert (IRL!(IR.LookaheadStart) == 3); + +//how many parameters follow the IR, should be optimized fixing some IR bits +int immediateParamsIR(IR i){ + switch (i){ + case IR.OrEnd,IR.InfiniteEnd,IR.InfiniteQEnd: + return 1; + case IR.RepeatEnd, IR.RepeatQEnd: + return 4; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + return 2; + default: + return 0; + } +} + +//full length of IR instruction inlcuding all parameters that might follow it +int lengthOfIR(IR i) +{ + return 1 + immediateParamsIR(i); +} + +//full length of the paired IR instruction inlcuding all parameters that might follow it +int lengthOfPairedIR(IR i) +{ + return 1 + immediateParamsIR(pairedIR(i)); +} + +//if the operation has a merge point (this relies on the order of the ops) +bool hasMerge(IR i) +{ + return (i&0b11)==0b10 && i <= IR.RepeatQEnd; +} + +//is an IR that opens a "group" +bool isStartIR(IR i) +{ + return (i&0b11)==0b01; +} + +//is an IR that ends a "group" +bool isEndIR(IR i) +{ + return (i&0b11)==0b10; +} + +//is a standalone IR +bool isAtomIR(IR i) +{ + return (i&0b11)==0b00; +} + +//makes respective pair out of IR i, swapping start/end bits of instruction +IR pairedIR(IR i) +{ + assert(isStartIR(i) || isEndIR(i)); + return cast(IR)(i ^ 0b11); +} + +//encoded IR instruction +struct Bytecode +{ + uint raw; + //natural constraints + enum maxSequence = 2+4; + enum maxData = 1<<22; + enum maxRaw = 1<<31; + + this(IR code, uint data) + { + assert(data < (1<<22) && code < 256); + raw = code<<24 | data; + } + + this(IR code, uint data, uint seq) + { + assert(data < (1<<22) && code < 256 ); + assert(seq >= 2 && seq < maxSequence); + raw = code << 24 | (seq - 2)<<22 | data; + } + + //store raw data + static Bytecode fromRaw(uint data) + { + Bytecode t; + t.raw = data; + return t; + } + + //bit twiddling helpers + //0-arg template due to @@@BUG@@@ 10985 + @property uint data()() const { return raw & 0x003f_ffff; } + + //ditto + //0-arg template due to @@@BUG@@@ 10985 + @property uint sequence()() const { return 2 + (raw >> 22 & 0x3); } + + //ditto + //0-arg template due to @@@BUG@@@ 10985 + @property IR code()() const { return cast(IR)(raw>>24); } + + //ditto + @property bool hotspot() const { return hasMerge(code); } + + //test the class of this instruction + @property bool isAtom() const { return isAtomIR(code); } + + //ditto + @property bool isStart() const { return isStartIR(code); } + + //ditto + @property bool isEnd() const { return isEndIR(code); } + + //number of arguments for this instruction + @property int args() const { return immediateParamsIR(code); } + + //mark this GroupStart or GroupEnd as referenced in backreference + void setBackrefence() + { + assert(code == IR.GroupStart || code == IR.GroupEnd); + raw = raw | 1 << 23; + } + + //is referenced + @property bool backreference() const + { + assert(code == IR.GroupStart || code == IR.GroupEnd); + return cast(bool)(raw & 1 << 23); + } + + //mark as local reference (for backrefs in lookarounds) + void setLocalRef() + { + assert(code == IR.Backref); + raw = raw | 1 << 23; + } + + //is a local ref + @property bool localRef() const + { + assert(code == IR.Backref); + return cast(bool)(raw & 1 << 23); + } + + //human readable name of instruction + @trusted @property string mnemonic()() const + {//@@@BUG@@@ to is @system + import std.conv; + return to!string(code); + } + + //full length of instruction + @property uint length() const + { + return lengthOfIR(code); + } + + //full length of respective start/end of this instruction + @property uint pairedLength() const + { + return lengthOfPairedIR(code); + } + + //returns bytecode of paired instruction (assuming this one is start or end) + @property Bytecode paired() const + {//depends on bit and struct layout order + assert(isStart || isEnd); + return Bytecode.fromRaw(raw ^ 0b11 << 24); + } + + //gets an index into IR block of the respective pair + uint indexOfPair(uint pc) const + { + assert(isStart || isEnd); + return isStart ? pc + data + length : pc - data - lengthOfPairedIR(code); + } +} + +static assert(Bytecode.sizeof == 4); + + +//index entry structure for name --> number of submatch +struct NamedGroup +{ + string name; + uint group; +} + +//holds pair of start-end markers for a submatch +struct Group(DataIndex) +{ + DataIndex begin, end; + @trusted string toString()() const + { + import std.format; + auto a = appender!string(); + formattedWrite(a, "%s..%s", begin, end); + return a.data; + } +} + +//debugging tool, prints out instruction along with opcodes +@trusted string disassemble(in Bytecode[] irb, uint pc, in NamedGroup[] dict=[]) +{ + import std.array, std.format; + auto output = appender!string(); + formattedWrite(output,"%s", irb[pc].mnemonic); + switch(irb[pc].code) + { + case IR.Char: + formattedWrite(output, " %s (0x%x)",cast(dchar)irb[pc].data, irb[pc].data); + break; + case IR.OrChar: + formattedWrite(output, " %s (0x%x) seq=%d", cast(dchar)irb[pc].data, irb[pc].data, irb[pc].sequence); + break; + case IR.RepeatStart, IR.InfiniteStart, IR.Option, IR.GotoEndOr, IR.OrStart: + //forward-jump instructions + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u", pc+len+IRL!(IR.RepeatStart)); + break; + case IR.RepeatEnd, IR.RepeatQEnd: //backward-jump instructions + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u min=%u max=%u step=%u", + pc - len, irb[pc + 3].raw, irb[pc + 4].raw, irb[pc + 2].raw); + break; + case IR.InfiniteEnd, IR.InfiniteQEnd, IR.OrEnd: //ditto + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u", pc-len); + break; + case IR.LookaheadEnd, IR.NeglookaheadEnd: //ditto + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u", pc-len); + break; + case IR.GroupStart, IR.GroupEnd: + uint n = irb[pc].data; + string name; + foreach(v;dict) + if(v.group == n) + { + name = "'"~v.name~"'"; + break; + } + formattedWrite(output, " %s #%u " ~ (irb[pc].backreference ? "referenced" : ""), + name, n); + break; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + uint len = irb[pc].data; + uint start = irb[pc+1].raw, end = irb[pc+2].raw; + formattedWrite(output, " pc=>%u [%u..%u]", pc + len + IRL!(IR.LookaheadStart), start, end); + break; + case IR.Backref: case IR.CodepointSet: case IR.Trie: + uint n = irb[pc].data; + formattedWrite(output, " %u", n); + if(irb[pc].code == IR.Backref) + formattedWrite(output, " %s", irb[pc].localRef ? "local" : "global"); + break; + default://all data-free instructions + } + if(irb[pc].hotspot) + formattedWrite(output, " Hotspot %u", irb[pc+1].raw); + return output.data; +} + +//disassemble the whole chunk +@trusted void printBytecode()(in Bytecode[] slice, in NamedGroup[] dict=[]) +{ + import std.stdio; + for(uint pc=0; pc= end; } + @property size_t length() { return end - start; } + alias opDollar = length; + @property NamedGroupRange save() + { + return NamedGroupRange(groups, start, end); + } + void popFront() { assert(!empty); start++; } + void popBack() { assert(!empty); end--; } + string opIndex()(size_t i) + { + assert(start + i < end, + "Requested named group is out of range."); + return groups[start+i].name; + } + NamedGroupRange opSlice(size_t low, size_t high) { + assert(low <= high); + assert(start + high <= end); + return NamedGroupRange(groups, start + low, start + high); + } + NamedGroupRange opSlice() { return this.save; } + } + return NamedGroupRange(dict, 0, dict.length); + } + +package: + import std.regex.internal.kickstart; //TODO: get rid of this dependency + NamedGroup[] dict; //maps name -> user group number + uint ngroup; //number of internal groups + uint maxCounterDepth; //max depth of nested {n,m} repetitions + uint hotspotTableSize; //number of entries in merge table + uint threadCount; + uint flags; //global regex flags + public const(Trie)[] tries; // + uint[] backrefed; //bit array of backreferenced submatches + Kickstart!Char kickstart; + + //bit access helper + uint isBackref(uint n) + { + if(n/32 >= backrefed.length) + return 0; + return backrefed[n / 32] & (1 << (n & 31)); + } + + //check if searching is not needed + void checkIfOneShot() + { + if(flags & RegexOption.multiline) + return; + L_CheckLoop: + for(uint i = 0; i < ir.length; i += ir[i].length) + { + switch(ir[i].code) + { + case IR.Bol: + flags |= RegexInfo.oneShot; + break L_CheckLoop; + case IR.GroupStart, IR.GroupEnd, IR.Eol, IR.Wordboundary, IR.Notwordboundary: + break; + default: + break L_CheckLoop; + } + } + } + + //print out disassembly a program's IR + @trusted debug(std_regex_parser) void print() const + {//@@@BUG@@@ write is system + for(uint i = 0; i < ir.length; i += ir[i].length) + { + writefln("%d\t%s ", i, disassemble(ir, i, dict)); + } + writeln("Total merge table size: ", hotspotTableSize); + writeln("Max counter nesting depth: ", maxCounterDepth); + } + +} + +//@@@BUG@@@ (unreduced) - public makes it inaccessible in std.regex.package (!) +/*public*/ struct StaticRegex(Char) +{ +package: + import std.regex.internal.backtracking; + alias Matcher = BacktrackingMatcher!(true); + alias MatchFn = bool function(ref Matcher!Char) @trusted; + MatchFn nativeFn; +public: + Regex!Char _regex; + alias _regex this; + this(Regex!Char re, MatchFn fn) + { + _regex = re; + nativeFn = fn; + + } + +} + +// The stuff below this point is temporarrily part of IR module +// but may need better place in the future (all internals) +package: + +//Simple UTF-string abstraction compatible with stream interface +struct Input(Char) + if(is(Char :dchar)) +{ + import std.utf; + alias DataIndex = size_t; + enum { isLoopback = false }; + alias String = const(Char)[]; + String _origin; + size_t _index; + + //constructs Input object out of plain string + this(String input, size_t idx = 0) + { + _origin = input; + _index = idx; + } + + //codepoint at current stream position + bool nextChar(ref dchar res, ref size_t pos) + { + pos = _index; + if(_index == _origin.length) + return false; + res = std.utf.decode(_origin, _index); + return true; + } + @property bool atEnd(){ + return _index == _origin.length; + } + bool search(Kickstart)(ref Kickstart kick, ref dchar res, ref size_t pos) + { + size_t idx = kick.search(_origin, _index); + _index = idx; + return nextChar(res, pos); + } + + //index of at End position + @property size_t lastIndex(){ return _origin.length; } + + //support for backtracker engine, might not be present + void reset(size_t index){ _index = index; } + + String opSlice(size_t start, size_t end){ return _origin[start..end]; } + + struct BackLooper + { + alias DataIndex = size_t; + enum { isLoopback = true }; + String _origin; + size_t _index; + this(Input input, size_t index) + { + _origin = input._origin; + _index = index; + } + @trusted bool nextChar(ref dchar res,ref size_t pos) + { + pos = _index; + if(_index == 0) + return false; + + res = _origin[0.._index].back; + _index -= std.utf.strideBack(_origin, _index); + + return true; + } + @property atEnd(){ return _index == 0 || _index == std.utf.strideBack(_origin, _index); } + auto loopBack(size_t index){ return Input(_origin, index); } + + //support for backtracker engine, might not be present + //void reset(size_t index){ _index = index ? index-std.utf.strideBack(_origin, index) : 0; } + void reset(size_t index){ _index = index; } + + String opSlice(size_t start, size_t end){ return _origin[end..start]; } + //index of at End position + @property size_t lastIndex(){ return 0; } + } + auto loopBack(size_t index){ return BackLooper(this, index); } +} + + +//both helpers below are internal, on its own are quite "explosive" +//unsafe, no initialization of elements +@system T[] mallocArray(T)(size_t len) +{ + import core.stdc.stdlib; + return (cast(T*)malloc(len * T.sizeof))[0 .. len]; +} + +//very unsafe, no initialization +@system T[] arrayInChunk(T)(size_t len, ref void[] chunk) +{ + auto ret = (cast(T*)chunk.ptr)[0..len]; + chunk = chunk[len * T.sizeof .. $]; + return ret; +} + +// +@trusted uint lookupNamedGroup(String)(NamedGroup[] dict, String name) +{//equal is @system? + import std.conv; + import std.algorithm : map, equal; + + auto fnd = assumeSorted!"cmp(a,b) < 0"(map!"a.name"(dict)).lowerBound(name).length; + enforce(fnd < dict.length && equal(dict[fnd].name, name), + text("no submatch named ", name)); + return dict[fnd].group; +} + +//whether ch is one of unicode newline sequences +//0-arg template due to @@@BUG@@@ 10985 +bool endOfLine()(dchar front, bool seenCr) +{ + return ((front == '\n') ^ seenCr) || front == '\r' + || front == NEL || front == LS || front == PS; +} + +// +//0-arg template due to @@@BUG@@@ 10985 +bool startOfLine()(dchar back, bool seenNl) +{ + return ((back == '\r') ^ seenNl) || back == '\n' + || back == NEL || back == LS || back == PS; +} + +//Test if bytecode starting at pc in program 're' can match given codepoint +//Returns: 0 - can't tell, -1 if doesn't match +int quickTestFwd(RegEx)(uint pc, dchar front, const ref RegEx re) +{ + static assert(IRL!(IR.OrChar) == 1);//used in code processing IR.OrChar + for(;;) + switch(re.ir[pc].code) + { + case IR.OrChar: + uint len = re.ir[pc].sequence; + uint end = pc + len; + if(re.ir[pc].data != front && re.ir[pc+1].data != front) + { + for(pc = pc+2; pc < end; pc++) + if(re.ir[pc].data == front) + break; + if(pc == end) + return -1; + } + return 0; + case IR.Char: + if(front == re.ir[pc].data) + return 0; + else + return -1; + case IR.Any: + return 0; + case IR.CodepointSet: + if(re.charsets[re.ir[pc].data].scanFor(front)) + return 0; + else + return -1; + case IR.GroupStart, IR.GroupEnd: + pc += IRL!(IR.GroupStart); + break; + case IR.Trie: + if(re.tries[re.ir[pc].data][front]) + return 0; + else + return -1; + default: + return 0; + } +} + +///Exception object thrown in case of errors during regex compilation. +public class RegexException : Exception +{ + /// + @trusted this(string msg, string file = __FILE__, size_t line = __LINE__) + {//@@@BUG@@@ Exception constructor is not @safe + super(msg, file, line); + } +} diff --git a/std/regex/internal/kickstart.d b/std/regex/internal/kickstart.d new file mode 100644 index 00000000000..ad6a8f41ead --- /dev/null +++ b/std/regex/internal/kickstart.d @@ -0,0 +1,546 @@ +/* + Kickstart is a coarse-grained "filter" engine that finds likely matches + to be verified by full-blown matcher. +*/ +module std.regex.internal.kickstart; + +package(std.regex): + +import std.regex.internal.ir; +import std.algorithm, std.range, std.utf; + +//utility for shiftOr, returns a minimum number of bytes to test in a Char +uint effectiveSize(Char)() +{ + static if(is(Char == char)) + return 1; + else static if(is(Char == wchar)) + return 2; + else static if(is(Char == dchar)) + return 3; + else + static assert(0); +} + +/* + Kickstart engine using ShiftOr algorithm, + a bit parallel technique for inexact string searching. +*/ +struct ShiftOr(Char) +{ +private: + uint[] table; + uint fChar; + uint n_length; + enum charSize = effectiveSize!Char(); + //maximum number of chars in CodepointSet to process + enum uint charsetThreshold = 32_000; + static struct ShiftThread + { + uint[] tab; + uint mask; + uint idx; + uint pc, counter, hops; + this(uint newPc, uint newCounter, uint[] table) + { + pc = newPc; + counter = newCounter; + mask = 1; + idx = 0; + hops = 0; + tab = table; + } + + void setMask(uint idx, uint mask) + { + tab[idx] |= mask; + } + + void setInvMask(uint idx, uint mask) + { + tab[idx] &= ~mask; + } + + void set(alias setBits = setInvMask)(dchar ch) + { + static if(charSize == 3) + { + uint val = ch, tmask = mask; + setBits(val&0xFF, tmask); + tmask <<= 1; + val >>= 8; + setBits(val&0xFF, tmask); + tmask <<= 1; + val >>= 8; + assert(val <= 0x10); + setBits(val, tmask); + tmask <<= 1; + } + else + { + Char[dchar.sizeof/Char.sizeof] buf; + uint tmask = mask; + size_t total = encode(buf, ch); + for(size_t i = 0; i < total; i++, tmask<<=1) + { + static if(charSize == 1) + setBits(buf[i], tmask); + else static if(charSize == 2) + { + setBits(buf[i]&0xFF, tmask); + tmask <<= 1; + setBits(buf[i]>>8, tmask); + } + } + } + } + void add(dchar ch){ return set!setInvMask(ch); } + void advance(uint s) + { + mask <<= s; + idx += s; + } + @property bool full(){ return !mask; } + } + + static ShiftThread fork(ShiftThread t, uint newPc, uint newCounter) + { + ShiftThread nt = t; + nt.pc = newPc; + nt.counter = newCounter; + return nt; + } + + @trusted static ShiftThread fetch(ref ShiftThread[] worklist) + { + auto t = worklist[$-1]; + worklist.length -= 1; + if(!__ctfe) + cast(void)worklist.assumeSafeAppend(); + return t; + } + + static uint charLen(uint ch) + { + assert(ch <= 0x10FFFF); + return codeLength!Char(cast(dchar)ch)*charSize; + } + +public: + @trusted this(ref Regex!Char re, uint[] memory) + { + import std.conv; + assert(memory.length == 256); + fChar = uint.max; + L_FindChar: + for(size_t i = 0;;) + { + switch(re.ir[i].code) + { + case IR.Char: + fChar = re.ir[i].data; + static if(charSize != 3) + { + Char[dchar.sizeof/Char.sizeof] buf; + encode(buf, fChar); + fChar = buf[0]; + } + fChar = fChar & 0xFF; + break L_FindChar; + case IR.GroupStart, IR.GroupEnd: + i += IRL!(IR.GroupStart); + break; + case IR.Bol, IR.Wordboundary, IR.Notwordboundary: + i += IRL!(IR.Bol); + break; + default: + break L_FindChar; + } + } + table = memory; + table[] = uint.max; + ShiftThread[] trs; + ShiftThread t = ShiftThread(0, 0, table); + //locate first fixed char if any + n_length = 32; + for(;;) + { + L_Eval_Thread: + for(;;) + { + switch(re.ir[t.pc].code) + { + case IR.Char: + uint s = charLen(re.ir[t.pc].data); + if(t.idx+s > n_length) + goto L_StopThread; + t.add(re.ir[t.pc].data); + t.advance(s); + t.pc += IRL!(IR.Char); + break; + case IR.OrChar://assumes IRL!(OrChar) == 1 + uint len = re.ir[t.pc].sequence; + uint end = t.pc + len; + uint[Bytecode.maxSequence] s; + uint numS; + for(uint i = 0; i < len; i++) + { + auto x = charLen(re.ir[t.pc+i].data); + if(countUntil(s[0..numS], x) < 0) + s[numS++] = x; + } + for(uint i = t.pc; i < end; i++) + { + t.add(re.ir[i].data); + } + for(uint i = 0; i < numS; i++) + { + auto tx = fork(t, t.pc + len, t.counter); + if(tx.idx + s[i] <= n_length) + { + tx.advance(s[i]); + trs ~= tx; + } + } + if(!trs.empty) + t = fetch(trs); + else + goto L_StopThread; + break; + case IR.CodepointSet: + case IR.Trie: + auto set = re.charsets[re.ir[t.pc].data]; + uint[4] s; + uint numS; + static if(charSize == 3) + { + s[0] = charSize; + numS = 1; + } + else + { + + static if(charSize == 1) + static immutable codeBounds = [0x0, 0x7F, 0x80, 0x7FF, 0x800, 0xFFFF, 0x10000, 0x10FFFF]; + else //== 2 + static immutable codeBounds = [0x0, 0xFFFF, 0x10000, 0x10FFFF]; + uint[] arr = new uint[set.byInterval.length * 2]; + size_t ofs = 0; + foreach(ival; set.byInterval) + { + arr[ofs++] = ival.a; + arr[ofs++] = ival.b; + } + auto srange = assumeSorted!"a <= b"(arr); + for(uint i = 0; i < codeBounds.length/2; i++) + { + auto start = srange.lowerBound(codeBounds[2*i]).length; + auto end = srange.lowerBound(codeBounds[2*i+1]).length; + if(end > start || (end == start && (end & 1))) + s[numS++] = (i+1)*charSize; + } + } + if(numS == 0 || t.idx + s[numS-1] > n_length) + goto L_StopThread; + auto chars = set.length; + if(chars > charsetThreshold) + goto L_StopThread; + foreach(ch; set.byCodepoint) + { + //avoid surrogate pairs + if(0xD800 <= ch && ch <= 0xDFFF) + continue; + t.add(ch); + } + for(uint i = 0; i < numS; i++) + { + auto tx = fork(t, t.pc + IRL!(IR.CodepointSet), t.counter); + tx.advance(s[i]); + trs ~= tx; + } + if(!trs.empty) + t = fetch(trs); + else + goto L_StopThread; + break; + case IR.Any: + goto L_StopThread; + + case IR.GotoEndOr: + t.pc += IRL!(IR.GotoEndOr)+re.ir[t.pc].data; + assert(re.ir[t.pc].code == IR.OrEnd); + goto case; + case IR.OrEnd: + t.pc += IRL!(IR.OrEnd); + break; + case IR.OrStart: + t.pc += IRL!(IR.OrStart); + goto case; + case IR.Option: + uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option); + //queue next Option + if(re.ir[next].code == IR.Option) + { + trs ~= fork(t, next, t.counter); + } + t.pc += IRL!(IR.Option); + break; + case IR.RepeatStart:case IR.RepeatQStart: + t.pc += IRL!(IR.RepeatStart)+re.ir[t.pc].data; + goto case IR.RepeatEnd; + case IR.RepeatEnd: + case IR.RepeatQEnd: + uint len = re.ir[t.pc].data; + uint step = re.ir[t.pc+2].raw; + uint min = re.ir[t.pc+3].raw; + if(t.counter < min) + { + t.counter += step; + t.pc -= len; + break; + } + uint max = re.ir[t.pc+4].raw; + if(t.counter < max) + { + trs ~= fork(t, t.pc - len, t.counter + step); + t.counter = t.counter%step; + t.pc += IRL!(IR.RepeatEnd); + } + else + { + t.counter = t.counter%step; + t.pc += IRL!(IR.RepeatEnd); + } + break; + case IR.InfiniteStart, IR.InfiniteQStart: + t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart); + goto case IR.InfiniteEnd; //both Q and non-Q + case IR.InfiniteEnd: + case IR.InfiniteQEnd: + uint len = re.ir[t.pc].data; + uint pc1, pc2; //branches to take in priority order + if(++t.hops == 32) + goto L_StopThread; + pc1 = t.pc + IRL!(IR.InfiniteEnd); + pc2 = t.pc - len; + trs ~= fork(t, pc2, t.counter); + t.pc = pc1; + break; + case IR.GroupStart, IR.GroupEnd: + t.pc += IRL!(IR.GroupStart); + break; + case IR.Bol, IR.Wordboundary, IR.Notwordboundary: + t.pc += IRL!(IR.Bol); + break; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + t.pc += IRL!(IR.LookaheadStart) + IRL!(IR.LookaheadEnd) + re.ir[t.pc].data; + break; + default: + L_StopThread: + assert(re.ir[t.pc].code >= 0x80, text(re.ir[t.pc].code)); + debug (fred_search) writeln("ShiftOr stumbled on ",re.ir[t.pc].mnemonic); + n_length = min(t.idx, n_length); + break L_Eval_Thread; + } + } + if(trs.empty) + break; + t = fetch(trs); + } + debug(std_regex_search) + { + writeln("Min length: ", n_length); + } + } + + @property bool empty() const { return n_length == 0; } + + @property uint length() const{ return n_length/charSize; } + + // lookup compatible bit pattern in haystack, return starting index + // has a useful trait: if supplied with valid UTF indexes, + // returns only valid UTF indexes + // (that given the haystack in question is valid UTF string) + @trusted size_t search(const(Char)[] haystack, size_t idx) + {//@BUG: apparently assumes little endian machines + import std.conv, core.stdc.string; + assert(!empty); + auto p = cast(const(ubyte)*)(haystack.ptr+idx); + uint state = uint.max; + uint limit = 1u<<(n_length - 1u); + debug(std_regex_search) writefln("Limit: %32b",limit); + if(fChar != uint.max) + { + const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length); + const orginalAlign = cast(size_t)p & (Char.sizeof-1); + while(p != end) + { + if(!~state) + {//speed up seeking first matching place + for(;;) + { + assert(p <= end, text(p," vs ", end)); + p = cast(ubyte*)memchr(p, fChar, end - p); + if(!p) + return haystack.length; + if((cast(size_t)p & (Char.sizeof-1)) == orginalAlign) + break; + if(++p == end) + return haystack.length; + } + state = ~1u; + assert((cast(size_t)p & (Char.sizeof-1)) == orginalAlign); + static if(charSize == 3) + { + state = (state<<1) | table[p[1]]; + state = (state<<1) | table[p[2]]; + p += 4; + } + else + p++; + //first char is tested, see if that's all + if(!(state & limit)) + return (p-cast(ubyte*)haystack.ptr)/Char.sizeof + -length; + } + else + {//have some bits/states for possible matches, + //use the usual shift-or cycle + static if(charSize == 3) + { + state = (state<<1) | table[p[0]]; + state = (state<<1) | table[p[1]]; + state = (state<<1) | table[p[2]]; + p += 4; + } + else + { + state = (state<<1) | table[p[0]]; + p++; + } + if(!(state & limit)) + return (p-cast(ubyte*)haystack.ptr)/Char.sizeof + -length; + } + debug(std_regex_search) writefln("State: %32b", state); + } + } + else + { + //normal path, partially unrolled for char/wchar + static if(charSize == 3) + { + const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length); + while(p != end) + { + state = (state<<1) | table[p[0]]; + state = (state<<1) | table[p[1]]; + state = (state<<1) | table[p[2]]; + p += 4; + if(!(state & limit))//division rounds down for dchar + return (p-cast(ubyte*)haystack.ptr)/Char.sizeof + -length; + } + } + else + { + auto len = cast(ubyte*)(haystack.ptr + haystack.length) - p; + size_t i = 0; + if(len & 1) + { + state = (state<<1) | table[p[i++]]; + if(!(state & limit)) + return idx+i/Char.sizeof-length; + } + while(i < len) + { + state = (state<<1) | table[p[i++]]; + if(!(state & limit)) + return idx+i/Char.sizeof + -length; + state = (state<<1) | table[p[i++]]; + if(!(state & limit)) + return idx+i/Char.sizeof + -length; + debug(std_regex_search) writefln("State: %32b", state); + } + } + } + return haystack.length; + } + + @system debug static void dump(uint[] table) + {//@@@BUG@@@ writef(ln) is @system + import std.stdio; + for(size_t i = 0; i < table.length; i += 4) + { + writefln("%32b %32b %32b %32b",table[i], table[i+1], table[i+2], table[i+3]); + } + } +} + +unittest +{ + import std.conv, std.regex; + @trusted void test_fixed(alias Kick)() + { + foreach(i, v; TypeTuple!(char, wchar, dchar)) + { + alias Char = v; + alias String = immutable(v)[]; + auto r = regex(to!String(`abc$`)); + auto kick = Kick!Char(r, new uint[256]); + assert(kick.length == 3, text(Kick.stringof," ",v.stringof, " == ", kick.length)); + auto r2 = regex(to!String(`(abc){2}a+`)); + kick = Kick!Char(r2, new uint[256]); + assert(kick.length == 7, text(Kick.stringof,v.stringof," == ", kick.length)); + auto r3 = regex(to!String(`\b(a{2}b{3}){2,4}`)); + kick = Kick!Char(r3, new uint[256]); + assert(kick.length == 10, text(Kick.stringof,v.stringof," == ", kick.length)); + auto r4 = regex(to!String(`\ba{2}c\bxyz`)); + kick = Kick!Char(r4, new uint[256]); + assert(kick.length == 6, text(Kick.stringof,v.stringof, " == ", kick.length)); + auto r5 = regex(to!String(`\ba{2}c\b`)); + kick = Kick!Char(r5, new uint[256]); + size_t x = kick.search("aabaacaa", 0); + assert(x == 3, text(Kick.stringof,v.stringof," == ", kick.length)); + x = kick.search("aabaacaa", x+1); + assert(x == 8, text(Kick.stringof,v.stringof," == ", kick.length)); + } + } + @trusted void test_flex(alias Kick)() + { + foreach(i, v;TypeTuple!(char, wchar, dchar)) + { + alias Char = v; + alias String = immutable(v)[]; + auto r = regex(to!String(`abc[a-z]`)); + auto kick = Kick!Char(r, new uint[256]); + auto x = kick.search(to!String("abbabca"), 0); + assert(x == 3, text("real x is ", x, " ",v.stringof)); + + auto r2 = regex(to!String(`(ax|bd|cdy)`)); + String s2 = to!String("abdcdyabax"); + kick = Kick!Char(r2, new uint[256]); + x = kick.search(s2, 0); + assert(x == 1, text("real x is ", x)); + x = kick.search(s2, x+1); + assert(x == 3, text("real x is ", x)); + x = kick.search(s2, x+1); + assert(x == 8, text("real x is ", x)); + auto rdot = regex(to!String(`...`)); + kick = Kick!Char(rdot, new uint[256]); + assert(kick.length == 0); + auto rN = regex(to!String(`a(b+|c+)x`)); + kick = Kick!Char(rN, new uint[256]); + assert(kick.length == 3); + assert(kick.search("ababx",0) == 2); + assert(kick.search("abaacba",0) == 3);//expected inexact + + } + } + test_fixed!(ShiftOr)(); + test_flex!(ShiftOr)(); +} + +alias Kickstart = ShiftOr; diff --git a/std/regex/internal/parser.d b/std/regex/internal/parser.d new file mode 100644 index 00000000000..d99985969b3 --- /dev/null +++ b/std/regex/internal/parser.d @@ -0,0 +1,1529 @@ +//Written in the D programming language +/* + Regular expression pattern parser. +*/ +module std.regex.internal.parser; + +import std.regex.internal.ir; +import std.algorithm, std.range, std.uni, std.typetuple, + std.traits, std.typecons, std.exception; + +// package relevant info from parser into a regex object +auto makeRegex(S)(Parser!S p) +{ + Regex!(BasicElementOf!S) re; + with(re) + { + ir = p.ir; + dict = p.dict; + ngroup = p.groupStack.top; + maxCounterDepth = p.counterDepth; + flags = p.re_flags; + charsets = p.charsets; + tries = p.tries; + backrefed = p.backrefed; + re.lightPostprocess(); + debug(std_regex_parser) + { + print(); + } + //@@@BUG@@@ (not reduced) + //somehow just using validate _collides_ with std.utf.validate (!) + version(assert) re.validateRe(); + } + return re; +} + +// helper for unittest +auto makeRegex(S)(S arg) + if(isSomeString!S) +{ + return makeRegex(Parser!S(arg, "")); +} + +unittest +{ + auto re = makeRegex(`(?P\w+) = (?P\d+)`); + auto nc = re.namedCaptures; + static assert(isRandomAccessRange!(typeof(nc))); + assert(!nc.empty); + assert(nc.length == 2); + assert(nc.equal(["name", "var"])); + assert(nc[0] == "name"); + assert(nc[1..$].equal(["var"])); + + re = makeRegex(`(\w+) (?P\w+) (\w+)`); + nc = re.namedCaptures; + assert(nc.length == 1); + assert(nc[0] == "named"); + assert(nc.front == "named"); + assert(nc.back == "named"); + + re = makeRegex(`(\w+) (\w+)`); + nc = re.namedCaptures; + assert(nc.empty); + + re = makeRegex(`(?P\d{4})/(?P\d{2})/(?P\d{2})/`); + nc = re.namedCaptures; + auto cp = nc.save; + assert(nc.equal(cp)); + nc.popFront(); + assert(nc.equal(cp[1..$])); + nc.popBack(); + assert(nc.equal(cp[1 .. $ - 1])); +} + + +@trusted void reverseBytecode()(Bytecode[] code) +{ + Bytecode[] rev = new Bytecode[code.length]; + uint revPc = cast(uint)rev.length; + Stack!(Tuple!(uint, uint, uint)) stack; + uint start = 0; + uint end = cast(uint)code.length; + for(;;) + { + for(uint pc = start; pc < end; ) + { + uint len = code[pc].length; + if(code[pc].code == IR.GotoEndOr) + break; //pick next alternation branch + if(code[pc].isAtom) + { + rev[revPc - len .. revPc] = code[pc .. pc + len]; + revPc -= len; + pc += len; + } + else if(code[pc].isStart || code[pc].isEnd) + { + //skip over other embedded lookbehinds they are reversed + if(code[pc].code == IR.LookbehindStart + || code[pc].code == IR.NeglookbehindStart) + { + uint blockLen = len + code[pc].data + + code[pc].pairedLength; + rev[revPc - blockLen .. revPc] = code[pc .. pc + blockLen]; + pc += blockLen; + revPc -= blockLen; + continue; + } + uint second = code[pc].indexOfPair(pc); + uint secLen = code[second].length; + rev[revPc - secLen .. revPc] = code[second .. second + secLen]; + revPc -= secLen; + if(code[pc].code == IR.OrStart) + { + //we pass len bytes forward, but secLen in reverse + uint revStart = revPc - (second + len - secLen - pc); + uint r = revStart; + uint i = pc + IRL!(IR.OrStart); + while(code[i].code == IR.Option) + { + if(code[i - 1].code != IR.OrStart) + { + assert(code[i - 1].code == IR.GotoEndOr); + rev[r - 1] = code[i - 1]; + } + rev[r] = code[i]; + auto newStart = i + IRL!(IR.Option); + auto newEnd = newStart + code[i].data; + auto newRpc = r + code[i].data + IRL!(IR.Option); + if(code[newEnd].code != IR.OrEnd) + { + newRpc--; + } + stack.push(tuple(newStart, newEnd, newRpc)); + r += code[i].data + IRL!(IR.Option); + i += code[i].data + IRL!(IR.Option); + } + pc = i; + revPc = revStart; + assert(code[pc].code == IR.OrEnd); + } + else + pc += len; + } + } + if(stack.empty) + break; + start = stack.top[0]; + end = stack.top[1]; + revPc = stack.top[2]; + stack.pop(); + } + code[] = rev[]; +} + + +alias Escapables = TypeTuple!('[', ']', '\\', '^', '$', '.', '|', '?', ',', '-', + ';', ':', '#', '&', '%', '/', '<', '>', '`', '*', '+', '(', ')', '{', '}', '~'); + +//test if a given string starts with hex number of maxDigit that's a valid codepoint +//returns it's value and skips these maxDigit chars on success, throws on failure +dchar parseUniHex(Char)(ref Char[] str, size_t maxDigit) +{ + //std.conv.parse is both @system and bogus + enforce(str.length >= maxDigit,"incomplete escape sequence"); + uint val; + for(int k = 0; k < maxDigit; k++) + { + auto current = str[k];//accepts ascii only, so it's OK to index directly + if('0' <= current && current <= '9') + val = val * 16 + current - '0'; + else if('a' <= current && current <= 'f') + val = val * 16 + current -'a' + 10; + else if('A' <= current && current <= 'F') + val = val * 16 + current - 'A' + 10; + else + throw new Exception("invalid escape sequence"); + } + enforce(val <= 0x10FFFF, "invalid codepoint"); + str = str[maxDigit..$]; + return val; +} + +@system unittest //BUG canFind is system +{ + string[] non_hex = [ "000j", "000z", "FffG", "0Z"]; + string[] hex = [ "01", "ff", "00af", "10FFFF" ]; + int[] value = [ 1, 0xFF, 0xAF, 0x10FFFF ]; + foreach(v; non_hex) + assert(collectException(parseUniHex(v, v.length)).msg + .canFind("invalid escape sequence")); + foreach(i, v; hex) + assert(parseUniHex(v, v.length) == value[i]); + string over = "0011FFFF"; + assert(collectException(parseUniHex(over, over.length)).msg + .canFind("invalid codepoint")); +} + +//heuristic value determines maximum CodepointSet length suitable for linear search +enum maxCharsetUsed = 6; + +enum maxCachedTries = 8; + +alias CodepointSetTrie!(13, 8) Trie; +alias codepointSetTrie!(13, 8) makeTrie; + +Trie[CodepointSet] trieCache; + +//accessor with caching +@trusted Trie getTrie(CodepointSet set) +{// @@@BUG@@@ 6357 almost all properties of AA are not @safe + if(__ctfe || maxCachedTries == 0) + return makeTrie(set); + else + { + auto p = set in trieCache; + if(p) + return *p; + if(trieCache.length == maxCachedTries) + { + // flush entries in trieCache + trieCache = null; + } + return (trieCache[set] = makeTrie(set)); + } +} + + +auto caseEnclose(CodepointSet set) +{ + auto cased = set & unicode.LC; + foreach (dchar ch; cased.byCodepoint) + { + foreach(c; simpleCaseFoldings(ch)) + set |= c; + } + return set; +} + +/+ + fetch codepoint set corresponding to a name (InBlock or binary property) ++/ +@trusted CodepointSet getUnicodeSet(in char[] name, bool negated, bool casefold) +{ + CodepointSet s = unicode(name); + //FIXME: caseEnclose for new uni as Set | CaseEnclose(SET && LC) + if(casefold) + s = caseEnclose(s); + if(negated) + s = s.inverted; + return s; +} + +//basic stack, just in case it gets used anywhere else then Parser +@trusted struct Stack(T) +{ + T[] data; + @property bool empty(){ return data.empty; } + + @property size_t length(){ return data.length; } + + void push(T val){ data ~= val; } + + T pop() + { + assert(!empty); + auto val = data[$ - 1]; + data = data[0 .. $ - 1]; + if(!__ctfe) + cast(void)data.assumeSafeAppend(); + return val; + } + + @property ref T top() + { + assert(!empty); + return data[$ - 1]; + } +} + +//safety limits +enum maxGroupNumber = 2^^19; +enum maxLookaroundDepth = 16; +// *Bytecode.sizeof, i.e. 1Mb of bytecode alone +enum maxCompiledLength = 2^^18; +//amounts to up to 4 Mb of auxilary table for matching +enum maxCumulativeRepetitionLength = 2^^20; + +struct Parser(R) + if (isForwardRange!R && is(ElementType!R : dchar)) +{ + enum infinite = ~0u; + dchar _current; + bool empty; + R pat, origin; //keep full pattern for pretty printing error messages + Bytecode[] ir; //resulting bytecode + uint re_flags = 0; //global flags e.g. multiline + internal ones + Stack!(uint) fixupStack; //stack of opened start instructions + NamedGroup[] dict; //maps name -> user group number + //current num of group, group nesting level and repetitions step + Stack!(uint) groupStack; + uint nesting = 0; + uint lookaroundNest = 0; + uint counterDepth = 0; //current depth of nested counted repetitions + CodepointSet[] charsets; // + const(Trie)[] tries; // + uint[] backrefed; //bitarray for groups + + @trusted this(S)(R pattern, S flags) + if(isSomeString!S) + { + pat = origin = pattern; + //reserve slightly more then avg as sampled from unittests + if(!__ctfe) + ir.reserve((pat.length*5+2)/4); + parseFlags(flags); + _current = ' ';//a safe default for freeform parsing + next(); + try + { + parseRegex(); + } + catch(Exception e) + { + error(e.msg);//also adds pattern location + } + put(Bytecode(IR.End, 0)); + } + + //mark referenced groups for latter processing + void markBackref(uint n) + { + if(n/32 >= backrefed.length) + backrefed.length = n/32 + 1; + backrefed[n / 32] |= 1 << (n & 31); + } + + bool isOpenGroup(uint n) + { + // walk the fixup stack and see if there are groups labeled 'n' + // fixup '0' is reserved for alternations + return fixupStack.data[1..$]. + canFind!(fix => ir[fix].code == IR.GroupStart && ir[fix].data == n)(); + } + + @property dchar current(){ return _current; } + + bool _next() + { + if(pat.empty) + { + empty = true; + return false; + } + _current = pat.front; + pat.popFront(); + return true; + } + + void skipSpace() + { + while(isWhite(current) && _next()){ } + } + + bool next() + { + if(re_flags & RegexOption.freeform) + { + bool r = _next(); + skipSpace(); + return r; + } + else + return _next(); + } + + void put(Bytecode code) + { + enforce(ir.length < maxCompiledLength, + "maximum compiled pattern length is exceeded"); + ir ~= code; + } + + void putRaw(uint number) + { + enforce(ir.length < maxCompiledLength, + "maximum compiled pattern length is exceeded"); + ir ~= Bytecode.fromRaw(number); + } + + //parsing number with basic overflow check + uint parseDecimal() + { + uint r = 0; + while(std.ascii.isDigit(current)) + { + if(r >= (uint.max/10)) + error("Overflow in decimal number"); + r = 10*r + cast(uint)(current-'0'); + if(!next()) + break; + } + return r; + } + + //parse control code of form \cXXX, c assumed to be the current symbol + dchar parseControlCode() + { + enforce(next(), "Unfinished escape sequence"); + enforce(('a' <= current && current <= 'z') || ('A' <= current && current <= 'Z'), + "Only letters are allowed after \\c"); + return current & 0x1f; + } + + // + @trusted void parseFlags(S)(S flags) + {//@@@BUG@@@ text is @system + import std.conv; + foreach(ch; flags)//flags are ASCII anyway + { + L_FlagSwitch: + switch(ch) + { + + foreach(i, op; __traits(allMembers, RegexOption)) + { + case RegexOptionNames[i]: + if(re_flags & mixin("RegexOption."~op)) + throw new RegexException(text("redundant flag specified: ",ch)); + re_flags |= mixin("RegexOption."~op); + break L_FlagSwitch; + } + default: + throw new RegexException(text("unknown regex flag '",ch,"'")); + } + } + } + + //parse and store IR for regex pattern + @trusted void parseRegex() + { + fixupStack.push(0); + groupStack.push(1);//0 - whole match + auto maxCounterDepth = counterDepth; + uint fix;//fixup pointer + + while(!empty) + { + debug(std_regex_parser) + writeln("*LR*\nSource: ", pat, "\nStack: ",fixupStack.stack.data); + switch(current) + { + case '(': + next(); + nesting++; + uint nglob; + fixupStack.push(cast(uint)ir.length); + if(current == '?') + { + next(); + switch(current) + { + case ':': + put(Bytecode(IR.Nop, 0)); + next(); + break; + case '=': + genLookaround(IR.LookaheadStart); + next(); + break; + case '!': + genLookaround(IR.NeglookaheadStart); + next(); + break; + case 'P': + next(); + if(current != '<') + error("Expected '<' in named group"); + string name; + if(!next() || !(isAlpha(current) || current == '_')) + error("Expected alpha starting a named group"); + name ~= current; + while(next() && (isAlpha(current) || + current == '_' || std.ascii.isDigit(current))) + { + name ~= current; + } + if(current != '>') + error("Expected '>' closing named group"); + next(); + nglob = groupStack.top++; + enforce(groupStack.top <= maxGroupNumber, "limit on submatches is exceeded"); + auto t = NamedGroup(name, nglob); + auto d = assumeSorted!"a.name < b.name"(dict); + auto ind = d.lowerBound(t).length; + insertInPlace(dict, ind, t); + put(Bytecode(IR.GroupStart, nglob)); + break; + case '<': + next(); + if(current == '=') + genLookaround(IR.LookbehindStart); + else if(current == '!') + genLookaround(IR.NeglookbehindStart); + else + error("'!' or '=' expected after '<'"); + next(); + break; + default: + error(" ':', '=', '<', 'P' or '!' expected after '(?' "); + } + } + else + { + nglob = groupStack.top++; + enforce(groupStack.top <= maxGroupNumber, "limit on number of submatches is exceeded"); + put(Bytecode(IR.GroupStart, nglob)); + } + break; + case ')': + enforce(nesting, "Unmatched ')'"); + nesting--; + next(); + fix = fixupStack.pop(); + switch(ir[fix].code) + { + case IR.GroupStart: + put(Bytecode(IR.GroupEnd,ir[fix].data)); + parseQuantifier(fix); + break; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + assert(lookaroundNest); + fixLookaround(fix); + lookaroundNest--; + break; + case IR.Option: //| xxx ) + //two fixups: last option + full OR + finishAlternation(fix); + fix = fixupStack.top; + switch(ir[fix].code) + { + case IR.GroupStart: + fixupStack.pop(); + put(Bytecode(IR.GroupEnd,ir[fix].data)); + parseQuantifier(fix); + break; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + assert(lookaroundNest); + lookaroundNest--; + fix = fixupStack.pop(); + fixLookaround(fix); + break; + default://(?:xxx) + fixupStack.pop(); + parseQuantifier(fix); + } + break; + default://(?:xxx) + parseQuantifier(fix); + } + break; + case '|': + next(); + fix = fixupStack.top; + if(ir.length > fix && ir[fix].code == IR.Option) + { + ir[fix] = Bytecode(ir[fix].code, cast(uint)ir.length - fix); + put(Bytecode(IR.GotoEndOr, 0)); + fixupStack.top = cast(uint)ir.length; //replace latest fixup for Option + put(Bytecode(IR.Option, 0)); + break; + } + uint len, orStart; + //start a new option + if(fixupStack.length == 1) + {//only root entry, effectively no fixup + len = cast(uint)ir.length + IRL!(IR.GotoEndOr); + orStart = 0; + } + else + {//IR.lookahead, etc. fixups that have length > 1, thus check ir[x].length + len = cast(uint)ir.length - fix - (ir[fix].length - 1); + orStart = fix + ir[fix].length; + } + insertInPlace(ir, orStart, Bytecode(IR.OrStart, 0), Bytecode(IR.Option, len)); + assert(ir[orStart].code == IR.OrStart); + put(Bytecode(IR.GotoEndOr, 0)); + fixupStack.push(orStart); //fixup for StartOR + fixupStack.push(cast(uint)ir.length); //for second Option + put(Bytecode(IR.Option, 0)); + break; + default://no groups or whatever + uint start = cast(uint)ir.length; + parseAtom(); + parseQuantifier(start); + } + } + + if(fixupStack.length != 1) + { + fix = fixupStack.pop(); + enforce(ir[fix].code == IR.Option, "no matching ')'"); + finishAlternation(fix); + enforce(fixupStack.length == 1, "no matching ')'"); + } + } + + //helper function, finalizes IR.Option, fix points to the first option of sequence + void finishAlternation(uint fix) + { + enforce(ir[fix].code == IR.Option, "no matching ')'"); + ir[fix] = Bytecode(ir[fix].code, cast(uint)ir.length - fix - IRL!(IR.OrStart)); + fix = fixupStack.pop(); + enforce(ir[fix].code == IR.OrStart, "no matching ')'"); + ir[fix] = Bytecode(IR.OrStart, cast(uint)ir.length - fix - IRL!(IR.OrStart)); + put(Bytecode(IR.OrEnd, cast(uint)ir.length - fix - IRL!(IR.OrStart))); + uint pc = fix + IRL!(IR.OrStart); + while(ir[pc].code == IR.Option) + { + pc = pc + ir[pc].data; + if(ir[pc].code != IR.GotoEndOr) + break; + ir[pc] = Bytecode(IR.GotoEndOr, cast(uint)(ir.length - pc - IRL!(IR.OrEnd))); + pc += IRL!(IR.GotoEndOr); + } + put(Bytecode.fromRaw(0)); + } + + //parse and store IR for atom-quantifier pair + @trusted void parseQuantifier(uint offset) + {//copy is @system + uint replace = ir[offset].code == IR.Nop; + if(empty && !replace) + return; + uint min, max; + switch(current) + { + case '*': + min = 0; + max = infinite; + break; + case '?': + min = 0; + max = 1; + break; + case '+': + min = 1; + max = infinite; + break; + case '{': + enforce(next(), "Unexpected end of regex pattern"); + enforce(std.ascii.isDigit(current), "First number required in repetition"); + min = parseDecimal(); + if(current == '}') + max = min; + else if(current == ',') + { + next(); + if(std.ascii.isDigit(current)) + max = parseDecimal(); + else if(current == '}') + max = infinite; + else + error("Unexpected symbol in regex pattern"); + skipSpace(); + if(current != '}') + error("Unmatched '{' in regex pattern"); + } + else + error("Unexpected symbol in regex pattern"); + if(min > max) + error("Illegal {n,m} quantifier"); + break; + default: + if(replace) + { + copy(ir[offset + 1 .. $], ir[offset .. $ - 1]); + ir.length -= 1; + } + return; + } + uint len = cast(uint)ir.length - offset - replace; + bool greedy = true; + //check only if we managed to get new symbol + if(next() && current == '?') + { + greedy = false; + next(); + } + if(max != infinite) + { + if(min != 1 || max != 1) + { + Bytecode op = Bytecode(greedy ? IR.RepeatStart : IR.RepeatQStart, len); + if(replace) + ir[offset] = op; + else + insertInPlace(ir, offset, op); + put(Bytecode(greedy ? IR.RepeatEnd : IR.RepeatQEnd, len)); + put(Bytecode.init); //hotspot + putRaw(1); + putRaw(min); + putRaw(max); + counterDepth = std.algorithm.max(counterDepth, nesting+1); + } + } + else if(min) //&& max is infinite + { + if(min != 1) + { + Bytecode op = Bytecode(greedy ? IR.RepeatStart : IR.RepeatQStart, len); + if(replace) + ir[offset] = op; + else + insertInPlace(ir, offset, op); + offset += 1;//so it still points to the repeated block + put(Bytecode(greedy ? IR.RepeatEnd : IR.RepeatQEnd, len)); + put(Bytecode.init); //hotspot + putRaw(1); + putRaw(min); + putRaw(min); + counterDepth = std.algorithm.max(counterDepth, nesting+1); + } + else if(replace) + { + copy(ir[offset+1 .. $], ir[offset .. $-1]); + ir.length -= 1; + } + put(Bytecode(greedy ? IR.InfiniteStart : IR.InfiniteQStart, len)); + enforce(ir.length + len < maxCompiledLength, "maximum compiled pattern length is exceeded"); + ir ~= ir[offset .. offset+len]; + //IR.InfinteX is always a hotspot + put(Bytecode(greedy ? IR.InfiniteEnd : IR.InfiniteQEnd, len)); + put(Bytecode.init); //merge index + } + else//vanila {0,inf} + { + Bytecode op = Bytecode(greedy ? IR.InfiniteStart : IR.InfiniteQStart, len); + if(replace) + ir[offset] = op; + else + insertInPlace(ir, offset, op); + //IR.InfinteX is always a hotspot + put(Bytecode(greedy ? IR.InfiniteEnd : IR.InfiniteQEnd, len)); + put(Bytecode.init); //merge index + + } + } + + //parse and store IR for atom + void parseAtom() + { + if(empty) + return; + switch(current) + { + case '*', '?', '+', '|', '{', '}': + error("'*', '+', '?', '{', '}' not allowed in atom"); + break; + case '.': + put(Bytecode(IR.Any, 0)); + next(); + break; + case '[': + parseCharset(); + break; + case '\\': + enforce(_next(), "Unfinished escape sequence"); + parseEscape(); + break; + case '^': + put(Bytecode(IR.Bol, 0)); + next(); + break; + case '$': + put(Bytecode(IR.Eol, 0)); + next(); + break; + default: + //FIXME: getCommonCasing in new std uni + if(re_flags & RegexOption.casefold) + { + auto range = simpleCaseFoldings(current); + assert(range.length <= 5); + if(range.length == 1) + put(Bytecode(IR.Char, range.front)); + else + foreach(v; range) + put(Bytecode(IR.OrChar, v, cast(uint)range.length)); + } + else + put(Bytecode(IR.Char, current)); + next(); + } + } + + //generate code for start of lookaround: (?= (?! (?<= (?= 0) + { + if(ivals.length*2 > maxCharsetUsed) + put(Bytecode(IR.Trie, cast(uint)n)); + else + put(Bytecode(IR.CodepointSet, cast(uint)n)); + return; + } + if(ivals.length*2 > maxCharsetUsed) + { + auto t = getTrie(set); + put(Bytecode(IR.Trie, cast(uint)tries.length)); + tries ~= t; + debug(std_regex_allocation) writeln("Trie generated"); + } + else + { + put(Bytecode(IR.CodepointSet, cast(uint)charsets.length)); + tries ~= Trie.init; + } + charsets ~= set; + assert(charsets.length == tries.length); + } + } + + //parse and generate IR for escape stand alone escape sequence + @trusted void parseEscape() + {//accesses array of appender + + switch(current) + { + case 'f': next(); put(Bytecode(IR.Char, '\f')); break; + case 'n': next(); put(Bytecode(IR.Char, '\n')); break; + case 'r': next(); put(Bytecode(IR.Char, '\r')); break; + case 't': next(); put(Bytecode(IR.Char, '\t')); break; + case 'v': next(); put(Bytecode(IR.Char, '\v')); break; + + case 'd': + next(); + charsetToIr(unicode.Nd); + break; + case 'D': + next(); + charsetToIr(unicode.Nd.inverted); + break; + case 'b': next(); put(Bytecode(IR.Wordboundary, 0)); break; + case 'B': next(); put(Bytecode(IR.Notwordboundary, 0)); break; + case 's': + next(); + charsetToIr(unicode.White_Space); + break; + case 'S': + next(); + charsetToIr(unicode.White_Space.inverted); + break; + case 'w': + next(); + charsetToIr(wordCharacter); + break; + case 'W': + next(); + charsetToIr(wordCharacter.inverted); + break; + case 'p': case 'P': + auto CodepointSet = parseUnicodePropertySpec(current == 'P'); + charsetToIr(CodepointSet); + break; + case 'x': + uint code = parseUniHex(pat, 2); + next(); + put(Bytecode(IR.Char,code)); + break; + case 'u': case 'U': + uint code = parseUniHex(pat, current == 'u' ? 4 : 8); + next(); + put(Bytecode(IR.Char, code)); + break; + case 'c': //control codes + Bytecode code = Bytecode(IR.Char, parseControlCode()); + next(); + put(code); + break; + case '0': + next(); + put(Bytecode(IR.Char, 0));//NUL character + break; + case '1': .. case '9': + uint nref = cast(uint)current - '0'; + uint maxBackref = sum(groupStack.data); + enforce(nref < maxBackref, "Backref to unseen group"); + //perl's disambiguation rule i.e. + //get next digit only if there is such group number + while(nref < maxBackref && next() && std.ascii.isDigit(current)) + { + nref = nref * 10 + current - '0'; + } + if(nref >= maxBackref) + nref /= 10; + enforce(!isOpenGroup(nref), "Backref to open group"); + uint localLimit = maxBackref - groupStack.top; + if(nref >= localLimit) + { + put(Bytecode(IR.Backref, nref-localLimit)); + ir[$-1].setLocalRef(); + } + else + put(Bytecode(IR.Backref, nref)); + markBackref(nref); + break; + default: + auto op = Bytecode(IR.Char, current); + next(); + put(op); + } + } + + //parse and return a CodepointSet for \p{...Property...} and \P{...Property..}, + //\ - assumed to be processed, p - is current + CodepointSet parseUnicodePropertySpec(bool negated) + { + enum MAX_PROPERTY = 128; + char[MAX_PROPERTY] result; + uint k = 0; + enforce(next()); + if(current == '{') + { + while(k < MAX_PROPERTY && next() && current !='}' && current !=':') + if(current != '-' && current != ' ' && current != '_') + result[k++] = cast(char)std.ascii.toLower(current); + enforce(k != MAX_PROPERTY, "invalid property name"); + enforce(current == '}', "} expected "); + } + else + {//single char properties e.g.: \pL, \pN ... + enforce(current < 0x80, "invalid property name"); + result[k++] = cast(char)current; + } + auto s = getUnicodeSet(result[0..k], negated, + cast(bool)(re_flags & RegexOption.casefold)); + enforce(!s.empty, "unrecognized unicode property spec"); + next(); + return s; + } + + // + @trusted void error(string msg) + { + import std.format; + auto app = appender!string(); + ir = null; + formattedWrite(app, "%s\nPattern with error: `%s` <--HERE-- `%s`", + msg, origin[0..$-pat.length], pat); + throw new RegexException(app.data); + } + + alias Char = BasicElementOf!R; + + @property program() + { + return makeRegex(this); + } +} + +/+ + lightweight post process step, + only essentials ++/ +@trusted void lightPostprocess(Char)(ref Regex!Char zis) +{//@@@BUG@@@ write is @system + with(zis) + { + struct FixedStack(T) + { + T[] arr; + uint _top; + //this(T[] storage){ arr = storage; _top = -1; } + @property ref T top(){ assert(!empty); return arr[_top]; } + void push(T x){ arr[++_top] = x; } + T pop() { assert(!empty); return arr[_top--]; } + @property bool empty(){ return _top == -1; } + } + auto counterRange = FixedStack!uint(new uint[maxCounterDepth+1], -1); + counterRange.push(1); + ulong cumRange = 0; + for(uint i = 0; i < ir.length; i += ir[i].length) + { + if(ir[i].hotspot) + { + assert(i + 1 < ir.length, + "unexpected end of IR while looking for hotspot"); + ir[i+1] = Bytecode.fromRaw(hotspotTableSize); + hotspotTableSize += counterRange.top; + } + switch(ir[i].code) + { + case IR.RepeatStart, IR.RepeatQStart: + uint repEnd = cast(uint)(i + ir[i].data + IRL!(IR.RepeatStart)); + assert(ir[repEnd].code == ir[i].paired.code); + uint max = ir[repEnd + 4].raw; + ir[repEnd+2].raw = counterRange.top; + ir[repEnd+3].raw *= counterRange.top; + ir[repEnd+4].raw *= counterRange.top; + ulong cntRange = cast(ulong)(max)*counterRange.top; + cumRange += cntRange; + enforce(cumRange < maxCumulativeRepetitionLength, + "repetition length limit is exceeded"); + counterRange.push(cast(uint)cntRange + counterRange.top); + threadCount += counterRange.top; + break; + case IR.RepeatEnd, IR.RepeatQEnd: + threadCount += counterRange.top; + counterRange.pop(); + break; + case IR.GroupStart: + if(isBackref(ir[i].data)) + ir[i].setBackrefence(); + threadCount += counterRange.top; + break; + case IR.GroupEnd: + if(isBackref(ir[i].data)) + ir[i].setBackrefence(); + threadCount += counterRange.top; + break; + default: + threadCount += counterRange.top; + } + } + checkIfOneShot(); + if(!(flags & RegexInfo.oneShot)) + kickstart = Kickstart!Char(zis, new uint[](256)); + debug(std_regex_allocation) writefln("IR processed, max threads: %d", threadCount); + } +} + +//IR code validator - proper nesting, illegal instructions, etc. +@trusted void validateRe(Char)(ref Regex!Char zis) +{//@@@BUG@@@ text is @system + import std.conv; + with(zis) + { + for(uint pc = 0; pc < ir.length; pc += ir[pc].length) + { + if(ir[pc].isStart || ir[pc].isEnd) + { + uint dest = ir[pc].indexOfPair(pc); + assert(dest < ir.length, text("Wrong length in opcode at pc=", + pc, " ", dest, " vs ", ir.length)); + assert(ir[dest].paired == ir[pc], + text("Wrong pairing of opcodes at pc=", pc, "and pc=", dest)); + } + else if(ir[pc].isAtom) + { + + } + else + assert(0, text("Unknown type of instruction at pc=", pc)); + } + } +} diff --git a/std/regex/internal/tests.d b/std/regex/internal/tests.d new file mode 100644 index 00000000000..334d23e1c29 --- /dev/null +++ b/std/regex/internal/tests.d @@ -0,0 +1,939 @@ +/* + Regualar expressions package test suite. +*/ +module std.regex.internal.tests; + +package(std.regex): + +import std.algorithm, std.conv, std.exception, std.range, std.typecons, + std.typetuple, std.regex; + +import std.regex.internal.parser : Escapables; // characters that need escaping + +alias Sequence(int B, int E) = staticIota!(B, E); + +unittest +{//sanity checks + regex("(a|b)*"); + regex(`(?:([0-9A-F]+)\.\.([0-9A-F]+)|([0-9A-F]+))\s*;\s*(.*)\s*#`); + regex("abc|edf|ighrg"); + auto r1 = regex("abc"); + auto r2 = regex("(gylba)"); + assert(match("abcdef", r1).hit == "abc"); + assert(!match("wida",r2)); + assert(bmatch("abcdef", r1).hit == "abc"); + assert(!bmatch("wida", r2)); + assert(match("abc", "abc".dup)); + assert(bmatch("abc", "abc".dup)); + Regex!char rc; + assert(rc.empty); + rc = regex("test"); + assert(!rc.empty); +} + +/* The test vectors in this file are altered from Henry Spencer's regexp + test code. His copyright notice is: + + Copyright (c) 1986 by University of Toronto. + Written by Henry Spencer. Not derived from licensed software. + + Permission is granted to anyone to use this software for any + purpose on any computer system, and to redistribute it freely, + subject to the following restrictions: + + 1. The author is not responsible for the consequences of use of + this software, no matter how awful, even if they arise + from defects in it. + + 2. The origin of this software must not be misrepresented, either + by explicit claim or by omission. + + 3. Altered versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + + */ + +unittest +{ + struct TestVectors + { + string pattern; + string input; + string result; + string format; + string replace; + string flags; + } + + enum TestVectors[] tv = [ + TestVectors( "a\\b", "a", "y", "$&", "a" ), + TestVectors( "(a)b\\1", "abaab","y", "$&", "aba" ), + TestVectors( "()b\\1", "aaab", "y", "$&", "b" ), + TestVectors( "abc", "abc", "y", "$&", "abc" ), + TestVectors( "abc", "xbc", "n", "-", "-" ), + TestVectors( "abc", "axc", "n", "-", "-" ), + TestVectors( "abc", "abx", "n", "-", "-" ), + TestVectors( "abc", "xabcy","y", "$&", "abc" ), + TestVectors( "abc", "ababc","y", "$&", "abc" ), + TestVectors( "ab*c", "abc", "y", "$&", "abc" ), + TestVectors( "ab*bc", "abc", "y", "$&", "abc" ), + TestVectors( "ab*bc", "abbc", "y", "$&", "abbc" ), + TestVectors( "ab*bc", "abbbbc","y", "$&", "abbbbc" ), + TestVectors( "ab+bc", "abbc", "y", "$&", "abbc" ), + TestVectors( "ab+bc", "abc", "n", "-", "-" ), + TestVectors( "ab+bc", "abq", "n", "-", "-" ), + TestVectors( "ab+bc", "abbbbc","y", "$&", "abbbbc" ), + TestVectors( "ab?bc", "abbc", "y", "$&", "abbc" ), + TestVectors( "ab?bc", "abc", "y", "$&", "abc" ), + TestVectors( "ab?bc", "abbbbc","n", "-", "-" ), + TestVectors( "ab?c", "abc", "y", "$&", "abc" ), + TestVectors( "^abc$", "abc", "y", "$&", "abc" ), + TestVectors( "^abc$", "abcc", "n", "-", "-" ), + TestVectors( "^abc", "abcc", "y", "$&", "abc" ), + TestVectors( "^abc$", "aabc", "n", "-", "-" ), + TestVectors( "abc$", "aabc", "y", "$&", "abc" ), + TestVectors( "^", "abc", "y", "$&", "" ), + TestVectors( "$", "abc", "y", "$&", "" ), + TestVectors( "a.c", "abc", "y", "$&", "abc" ), + TestVectors( "a.c", "axc", "y", "$&", "axc" ), + TestVectors( "a.*c", "axyzc","y", "$&", "axyzc" ), + TestVectors( "a.*c", "axyzd","n", "-", "-" ), + TestVectors( "a[bc]d", "abc", "n", "-", "-" ), + TestVectors( "a[bc]d", "abd", "y", "$&", "abd" ), + TestVectors( "a[b-d]e", "abd", "n", "-", "-" ), + TestVectors( "a[b-d]e", "ace", "y", "$&", "ace" ), + TestVectors( "a[b-d]", "aac", "y", "$&", "ac" ), + TestVectors( "a[-b]", "a-", "y", "$&", "a-" ), + TestVectors( "a[b-]", "a-", "y", "$&", "a-" ), + TestVectors( "a[b-a]", "-", "c", "-", "-" ), + TestVectors( "a[]b", "-", "c", "-", "-" ), + TestVectors( "a[", "-", "c", "-", "-" ), + TestVectors( "a]", "a]", "y", "$&", "a]" ), + TestVectors( "a[\\]]b", "a]b", "y", "$&", "a]b" ), + TestVectors( "a[^bc]d", "aed", "y", "$&", "aed" ), + TestVectors( "a[^bc]d", "abd", "n", "-", "-" ), + TestVectors( "a[^-b]c", "adc", "y", "$&", "adc" ), + TestVectors( "a[^-b]c", "a-c", "n", "-", "-" ), + TestVectors( "a[^\\]b]c", "adc", "y", "$&", "adc" ), + TestVectors( "ab|cd", "abc", "y", "$&", "ab" ), + TestVectors( "ab|cd", "abcd", "y", "$&", "ab" ), + TestVectors( "()ef", "def", "y", "$&-$1", "ef-" ), + TestVectors( "()*", "-", "y", "-", "-" ), + TestVectors( "*a", "-", "c", "-", "-" ), + TestVectors( "^*", "-", "y", "-", "-" ), + TestVectors( "$*", "-", "y", "-", "-" ), + TestVectors( "(*)b", "-", "c", "-", "-" ), + TestVectors( "$b", "b", "n", "-", "-" ), + TestVectors( "a\\", "-", "c", "-", "-" ), + TestVectors( "a\\(b", "a(b", "y", "$&-$1", "a(b-" ), + TestVectors( "a\\(*b", "ab", "y", "$&", "ab" ), + TestVectors( "a\\(*b", "a((b", "y", "$&", "a((b" ), + TestVectors( "a\\\\b", "a\\b", "y", "$&", "a\\b" ), + TestVectors( "abc)", "-", "c", "-", "-" ), + TestVectors( "(abc", "-", "c", "-", "-" ), + TestVectors( "((a))", "abc", "y", "$&-$1-$2", "a-a-a" ), + TestVectors( "(a)b(c)", "abc", "y", "$&-$1-$2", "abc-a-c" ), + TestVectors( "a+b+c", "aabbabc","y", "$&", "abc" ), + TestVectors( "a**", "-", "c", "-", "-" ), + TestVectors( "a*?a", "aa", "y", "$&", "a" ), + TestVectors( "(a*)*", "aaa", "y", "-", "-" ), + TestVectors( "(a*)+", "aaa", "y", "-", "-" ), + TestVectors( "(a|)*", "-", "y", "-", "-" ), + TestVectors( "(a*|b)*", "aabb", "y", "-", "-" ), + TestVectors( "(a|b)*", "ab", "y", "$&-$1", "ab-b" ), + TestVectors( "(a+|b)*", "ab", "y", "$&-$1", "ab-b" ), + TestVectors( "(a+|b)+", "ab", "y", "$&-$1", "ab-b" ), + TestVectors( "(a+|b)?", "ab", "y", "$&-$1", "a-a" ), + TestVectors( "[^ab]*", "cde", "y", "$&", "cde" ), + TestVectors( "(^)*", "-", "y", "-", "-" ), + TestVectors( "(ab|)*", "-", "y", "-", "-" ), + TestVectors( ")(", "-", "c", "-", "-" ), + TestVectors( "", "abc", "y", "$&", "" ), + TestVectors( "abc", "", "n", "-", "-" ), + TestVectors( "a*", "", "y", "$&", "" ), + TestVectors( "([abc])*d", "abbbcd", "y", "$&-$1", "abbbcd-c" ), + TestVectors( "([abc])*bcd", "abcd", "y", "$&-$1", "abcd-a" ), + TestVectors( "a|b|c|d|e", "e", "y", "$&", "e" ), + TestVectors( "(a|b|c|d|e)f", "ef", "y", "$&-$1", "ef-e" ), + TestVectors( "((a*|b))*", "aabb", "y", "-", "-" ), + TestVectors( "abcd*efg", "abcdefg", "y", "$&", "abcdefg" ), + TestVectors( "ab*", "xabyabbbz", "y", "$&", "ab" ), + TestVectors( "ab*", "xayabbbz", "y", "$&", "a" ), + TestVectors( "(ab|cd)e", "abcde", "y", "$&-$1", "cde-cd" ), + TestVectors( "[abhgefdc]ij", "hij", "y", "$&", "hij" ), + TestVectors( "^(ab|cd)e", "abcde", "n", "x$1y", "xy" ), + TestVectors( "(abc|)ef", "abcdef", "y", "$&-$1", "ef-" ), + TestVectors( "(a|b)c*d", "abcd", "y", "$&-$1", "bcd-b" ), + TestVectors( "(ab|ab*)bc", "abc", "y", "$&-$1", "abc-a" ), + TestVectors( "a([bc]*)c*", "abc", "y", "$&-$1", "abc-bc" ), + TestVectors( "a([bc]*)(c*d)", "abcd", "y", "$&-$1-$2", "abcd-bc-d" ), + TestVectors( "a([bc]+)(c*d)", "abcd", "y", "$&-$1-$2", "abcd-bc-d" ), + TestVectors( "a([bc]*)(c+d)", "abcd", "y", "$&-$1-$2", "abcd-b-cd" ), + TestVectors( "a[bcd]*dcdcde", "adcdcde", "y", "$&", "adcdcde" ), + TestVectors( "a[bcd]+dcdcde", "adcdcde", "n", "-", "-" ), + TestVectors( "(ab|a)b*c", "abc", "y", "$&-$1", "abc-ab" ), + TestVectors( "((a)(b)c)(d)", "abcd", "y", "$1-$2-$3-$4", "abc-a-b-d" ), + TestVectors( "[a-zA-Z_][a-zA-Z0-9_]*", "alpha", "y", "$&", "alpha" ), + TestVectors( "^a(bc+|b[eh])g|.h$", "abh", "y", "$&-$1", "bh-" ), + TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "effgz", "y", "$&-$1-$2", "effgz-effgz-" ), + TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "ij", "y", "$&-$1-$2", "ij-ij-j" ), + TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "effg", "n", "-", "-" ), + TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "bcdd", "n", "-", "-" ), + TestVectors( "(bc+d$|ef*g.|h?i(j|k))", "reffgz", "y", "$&-$1-$2", "effgz-effgz-" ), + TestVectors( "(((((((((a)))))))))", "a", "y", "$&", "a" ), + TestVectors( "multiple words of text", "uh-uh", "n", "-", "-" ), + TestVectors( "multiple words", "multiple words, yeah", "y", "$&", "multiple words" ), + TestVectors( "(.*)c(.*)", "abcde", "y", "$&-$1-$2", "abcde-ab-de" ), + TestVectors( "\\((.*), (.*)\\)", "(a, b)", "y", "($2, $1)", "(b, a)" ), + TestVectors( "abcd", "abcd", "y", "$&-&-$$$&", "abcd-&-$abcd" ), + TestVectors( "a(bc)d", "abcd", "y", "$1-$$1-$$$1", "bc-$1-$bc" ), + TestVectors( "[k]", "ab", "n", "-", "-" ), + TestVectors( "[ -~]*", "abc", "y", "$&", "abc" ), + TestVectors( "[ -~ -~]*", "abc", "y", "$&", "abc" ), + TestVectors( "[ -~ -~ -~]*", "abc", "y", "$&", "abc" ), + TestVectors( "[ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), + TestVectors( "[ -~ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), + TestVectors( "[ -~ -~ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), + TestVectors( "[ -~ -~ -~ -~ -~ -~ -~]*", "abc", "y", "$&", "abc" ), + TestVectors( "a{2}", "candy", "n", "", "" ), + TestVectors( "a{2}", "caandy", "y", "$&", "aa" ), + TestVectors( "a{2}", "caaandy", "y", "$&", "aa" ), + TestVectors( "a{2,}", "candy", "n", "", "" ), + TestVectors( "a{2,}", "caandy", "y", "$&", "aa" ), + TestVectors( "a{2,}", "caaaaaandy", "y", "$&", "aaaaaa" ), + TestVectors( "a{1,3}", "cndy", "n", "", "" ), + TestVectors( "a{1,3}", "candy", "y", "$&", "a" ), + TestVectors( "a{1,3}", "caandy", "y", "$&", "aa" ), + TestVectors( "a{1,3}", "caaaaaandy", "y", "$&", "aaa" ), + TestVectors( "e?le?", "angel", "y", "$&", "el" ), + TestVectors( "e?le?", "angle", "y", "$&", "le" ), + TestVectors( "\\bn\\w", "noonday", "y", "$&", "no" ), + TestVectors( "\\wy\\b", "possibly yesterday", "y", "$&", "ly" ), + TestVectors( "\\w\\Bn", "noonday", "y", "$&", "on" ), + TestVectors( "y\\B\\w", "possibly yesterday", "y", "$&", "ye" ), + TestVectors( "\\cJ", "abc\ndef", "y", "$&", "\n" ), + TestVectors( "\\d", "B2 is", "y", "$&", "2" ), + TestVectors( "\\D", "B2 is", "y", "$&", "B" ), + TestVectors( "\\s\\w*", "foo bar", "y", "$&", " bar" ), + TestVectors( "\\S\\w*", "foo bar", "y", "$&", "foo" ), + TestVectors( "abc", "ababc", "y", "$&", "abc" ), + TestVectors( "apple(,)\\sorange\\1", "apple, orange, cherry, peach", "y", "$&", "apple, orange," ), + TestVectors( "(\\w+)\\s(\\w+)", "John Smith", "y", "$2, $1", "Smith, John" ), + TestVectors( "\\n\\f\\r\\t\\v", "abc\n\f\r\t\vdef", "y", "$&", "\n\f\r\t\v" ), + TestVectors( ".*c", "abcde", "y", "$&", "abc" ), + TestVectors( "^\\w+((;|=)\\w+)+$", "some=host=tld", "y", "$&-$1-$2", "some=host=tld-=tld-=" ), + TestVectors( "^\\w+((\\.|-)\\w+)+$", "some.host.tld", "y", "$&-$1-$2", "some.host.tld-.tld-." ), + TestVectors( "q(a|b)*q", "xxqababqyy", "y", "$&-$1", "qababq-b" ), + TestVectors( "^(a)(b){0,1}(c*)", "abcc", "y", "$1 $2 $3", "a b cc" ), + TestVectors( "^(a)((b){0,1})(c*)", "abcc", "y", "$1 $2 $3", "a b b" ), + TestVectors( "^(a)(b)?(c*)", "abcc", "y", "$1 $2 $3", "a b cc" ), + TestVectors( "^(a)((b)?)(c*)", "abcc", "y", "$1 $2 $3", "a b b" ), + TestVectors( "^(a)(b){0,1}(c*)", "acc", "y", "$1 $2 $3", "a cc" ), + TestVectors( "^(a)((b){0,1})(c*)", "acc", "y", "$1 $2 $3", "a " ), + TestVectors( "^(a)(b)?(c*)", "acc", "y", "$1 $2 $3", "a cc" ), + TestVectors( "^(a)((b)?)(c*)", "acc", "y", "$1 $2 $3", "a " ), + TestVectors( "(?:ab){3}", "_abababc","y", "$&-$1", "ababab-" ), + TestVectors( "(?:a(?:x)?)+", "aaxaxx", "y", "$&-$1-$2", "aaxax--" ), + TestVectors( `\W\w\W`, "aa b!ca", "y", "$&", " b!"), +//more repetitions: + TestVectors( "(?:a{2,4}b{1,3}){1,2}", "aaabaaaabbb", "y", "$&", "aaabaaaabbb" ), + TestVectors( "(?:a{2,4}b{1,3}){1,2}?", "aaabaaaabbb", "y", "$&", "aaab" ), +//groups: + TestVectors( "(abc)|(edf)|(xyz)", "xyz", "y", "$1-$2-$3","--xyz"), + TestVectors( "(?P\\d+)/(?P\\d+)", "2/3", "y", "${d}/${q}", "3/2"), +//set operations: + TestVectors( "[a-z--d-f]", " dfa", "y", "$&", "a"), + TestVectors( "[abc[pq--acq]]{2}", "bqpaca", "y", "$&", "pa"), + TestVectors( "[a-z9&&abc0-9]{3}", "z90a0abc", "y", "$&", "abc"), + TestVectors( "[0-9a-f~~0-5a-z]{2}", "g0a58x", "y", "$&", "8x"), + TestVectors( "[abc[pq]xyz[rs]]{4}", "cqxr", "y", "$&", "cqxr"), + TestVectors( "[abcdf--[ab&&[bcd]][acd]]", "abcdefgh", "y", "$&", "f"), +//unicode blocks & properties: + TestVectors( `\P{Inlatin1suppl ement}`, "\u00c2!", "y", "$&", "!"), + TestVectors( `\p{InLatin-1 Supplement}\p{in-mathematical-operators}\P{Inlatin1suppl ement}`, "\u00c2\u2200\u00c3\u2203.", "y", "$&", "\u00c3\u2203."), + TestVectors( `[-+*/\p{in-mathematical-operators}]{2}`, "a+\u2212", "y", "$&", "+\u2212"), + TestVectors( `\p{Ll}+`, "XabcD", "y", "$&", "abc"), + TestVectors( `\p{Lu}+`, "абвГДЕ", "y", "$&", "ГДЕ"), + TestVectors( `^\p{Currency Symbol}\p{Sc}`, "$₤", "y", "$&", "$₤"), + TestVectors( `\p{Common}\p{Thai}`, "!ฆ", "y", "$&", "!ฆ"), + TestVectors( `[\d\s]*\D`, "12 \t3\U00001680\u0F20_2", "y", "$&", "12 \t3\U00001680\u0F20_"), + TestVectors( `[c-wф]фф`, "ффф", "y", "$&", "ффф"), +//case insensitive: + TestVectors( `^abcdEf$`, "AbCdEF", "y", "$&", "AbCdEF", "i"), + TestVectors( `Русский язык`, "рУсскИй ЯзЫк", "y", "$&", "рУсскИй ЯзЫк", "i"), + TestVectors( `ⒶⒷⓒ` , "ⓐⓑⒸ", "y", "$&", "ⓐⓑⒸ", "i"), + TestVectors( "\U00010400{2}", "\U00010428\U00010400 ", "y", "$&", "\U00010428\U00010400", "i"), + TestVectors( `[adzУ-Я]{4}`, "DzюЯ", "y", "$&", "DzюЯ", "i"), + TestVectors( `\p{L}\p{Lu}{10}`, "абвгдеЖЗИКЛ", "y", "$&", "абвгдеЖЗИКЛ", "i"), + TestVectors( `(?:Dåb){3}`, "DåbDÅBdÅb", "y", "$&", "DåbDÅBdÅb", "i"), +//escapes: + TestVectors( `\u0041\u005a\U00000065\u0001`, "AZe\u0001", "y", "$&", "AZe\u0001"), + TestVectors( `\u`, "", "c", "-", "-"), + TestVectors( `\U`, "", "c", "-", "-"), + TestVectors( `\u003`, "", "c", "-", "-"), + TestVectors( `[\x00-\x7f]{4}`, "\x00\x09ab", "y", "$&", "\x00\x09ab"), + TestVectors( `[\cJ\cK\cA-\cD]{3}\cQ`, "\x01\x0B\x0A\x11", "y", "$&", "\x01\x0B\x0A\x11"), + TestVectors( `\r\n\v\t\f\\`, "\r\n\v\t\f\\", "y", "$&", "\r\n\v\t\f\\"), + TestVectors( `[\u0003\u0001]{2}`, "\u0001\u0003", "y", "$&", "\u0001\u0003"), + TestVectors( `^[\u0020-\u0080\u0001\n-\r]{8}`, "abc\u0001\v\f\r\n", "y", "$&", "abc\u0001\v\f\r\n"), + TestVectors( `\w+\S\w+`, "ab7!44c", "y", "$&", "ab7!44c"), + TestVectors( `\b\w+\b`, " abde4 ", "y", "$&", "abde4"), + TestVectors( `\b\w+\b`, " abde4", "y", "$&", "abde4"), + TestVectors( `\b\w+\b`, "abde4 ", "y", "$&", "abde4"), + TestVectors( `\pL\pS`, "a\u02DA", "y", "$&", "a\u02DA"), + TestVectors( `\pX`, "", "c", "-", "-"), +// ^, $, \b, \B, multiline : + TestVectors( `\r.*?$`, "abc\r\nxy", "y", "$&", "\r\nxy", "sm"), + TestVectors( `^a$^b$`, "a\r\nb\n", "n", "$&", "-", "m"), + TestVectors( `^a$\r\n^b$`,"a\r\nb\n", "y", "$&", "a\r\nb", "m"), + TestVectors( `^$`, "\r\n", "y", "$&", "", "m"), + TestVectors( `^a$\nx$`, "a\nx\u2028","y", "$&", "a\nx", "m"), + TestVectors( `^a$\nx$`, "a\nx\u2029","y", "$&", "a\nx", "m"), + TestVectors( `^a$\nx$`, "a\nx\u0085","y", "$&", "a\nx","m"), + TestVectors( `^x$`, "\u2028x", "y", "$&", "x", "m"), + TestVectors( `^x$`, "\u2029x", "y", "$&", "x", "m"), + TestVectors( `^x$`, "\u0085x", "y", "$&", "x", "m"), + TestVectors( `\b^.`, "ab", "y", "$&", "a"), + TestVectors( `\B^.`, "ab", "n", "-", "-"), + TestVectors( `^ab\Bc\B`, "\r\nabcd", "y", "$&", "abc", "m"), + TestVectors( `^.*$`, "12345678", "y", "$&", "12345678"), + +// luckily obtained regression on incremental matching in backtracker + TestVectors( `^(?:(?:([0-9A-F]+)\.\.([0-9A-F]+)|([0-9A-F]+))\s*;\s*([^ ]*)\s*#|# (?:\w|_)+=((?:\w|_)+))`, + "0020 ; White_Space # ", "y", "$1-$2-$3", "--0020"), +//lookahead + TestVectors( "(foo.)(?=(bar))", "foobar foodbar", "y", "$&-$1-$2", "food-food-bar" ), + TestVectors( `\b(\d+)[a-z](?=\1)`, "123a123", "y", "$&-$1", "123a-123" ), + TestVectors( `\$(?!\d{3})\w+`, "$123 $abc", "y", "$&", "$abc"), + TestVectors( `(abc)(?=(ed(f))\3)`, "abcedff", "y", "-", "-"), + TestVectors( `\b[A-Za-z0-9.]+(?=(@(?!gmail)))`, "a@gmail,x@com", "y", "$&-$1", "x-@"), + TestVectors( `x()(abc)(?=(d)(e)(f)\2)`, "xabcdefabc", "y", "$&", "xabc"), + TestVectors( `x()(abc)(?=(d)(e)(f)()\3\4\5)`, "xabcdefdef", "y", "$&", "xabc"), +//lookback + TestVectors( `(?<=(ab))\d`, "12ba3ab4", "y", "$&-$1", "4-ab", "i"), + TestVectors( `\w(?"); + assert(bmatch("texttext", greed).hit + == "text"); + auto cr8 = ctRegex!("^(a)(b)?(c*)"); + auto m8 = bmatch("abcc",cr8); + assert(m8); + assert(m8.captures[1] == "a"); + assert(m8.captures[2] == "b"); + assert(m8.captures[3] == "cc"); + auto cr9 = ctRegex!("q(a|b)*q"); + auto m9 = match("xxqababqyy",cr9); + assert(m9); + assert(equal(bmatch("xxqababqyy",cr9).captures, ["qababq", "b"])); + + auto rtr = regex("a|b|c"); + enum ctr = regex("a|b|c"); + assert(equal(rtr.ir,ctr.ir)); + //CTFE parser BUG is triggered by group + //in the middle of alternation (at least not first and not last) + enum testCT = regex(`abc|(edf)|xyz`); + auto testRT = regex(`abc|(edf)|xyz`); + assert(equal(testCT.ir,testRT.ir)); +} + +unittest +{ + enum cx = ctRegex!"(A|B|C)"; + auto mx = match("B",cx); + assert(mx); + assert(equal(mx.captures, [ "B", "B"])); + enum cx2 = ctRegex!"(A|B)*"; + assert(match("BAAA",cx2)); + + enum cx3 = ctRegex!("a{3,4}","i"); + auto mx3 = match("AaA",cx3); + assert(mx3); + assert(mx3.captures[0] == "AaA"); + enum cx4 = ctRegex!(`^a{3,4}?[a-zA-Z0-9~]{1,2}`,"i"); + auto mx4 = match("aaaabc", cx4); + assert(mx4); + assert(mx4.captures[0] == "aaaab"); + auto cr8 = ctRegex!("(a)(b)?(c*)"); + auto m8 = bmatch("abcc",cr8); + assert(m8); + assert(m8.captures[1] == "a"); + assert(m8.captures[2] == "b"); + assert(m8.captures[3] == "cc"); + auto cr9 = ctRegex!(".*$", "gm"); + auto m9 = match("First\rSecond", cr9); + assert(m9); + assert(equal(map!"a.hit"(m9), ["First", "", "Second"])); +} + +unittest +{ +//global matching + void test_body(alias matchFn)() + { + string s = "a quick brown fox jumps over a lazy dog"; + auto r1 = regex("\\b[a-z]+\\b","g"); + string[] test; + foreach(m; matchFn(s, r1)) + test ~= m.hit; + assert(equal(test, [ "a", "quick", "brown", "fox", "jumps", "over", "a", "lazy", "dog"])); + auto free_reg = regex(` + + abc + \s+ + " + ( + [^"]+ + | \\ " + )+ + " + z + `, "x"); + auto m = match(`abc "quoted string with \" inside"z`,free_reg); + assert(m); + string mails = " hey@you.com no@spam.net "; + auto rm = regex(`@(?<=\S+@)\S+`,"g"); + assert(equal(map!"a[0]"(matchFn(mails, rm)), ["@you.com", "@spam.net"])); + auto m2 = matchFn("First line\nSecond line",regex(".*$","gm")); + assert(equal(map!"a[0]"(m2), ["First line", "", "Second line"])); + auto m2a = matchFn("First line\nSecond line",regex(".+$","gm")); + assert(equal(map!"a[0]"(m2a), ["First line", "Second line"])); + auto m2b = matchFn("First line\nSecond line",regex(".+?$","gm")); + assert(equal(map!"a[0]"(m2b), ["First line", "Second line"])); + debug(std_regex_test) writeln("!!! FReD FLAGS test done "~matchFn.stringof~" !!!"); + } + test_body!bmatch(); + test_body!match(); +} + +//tests for accumulated std.regex issues and other regressions +unittest +{ + void test_body(alias matchFn)() + { + //issue 5857 + //matching goes out of control if ... in (...){x} has .*/.+ + auto c = matchFn("axxxzayyyyyzd",regex("(a.*z){2}d")).captures; + assert(c[0] == "axxxzayyyyyzd"); + assert(c[1] == "ayyyyyz"); + auto c2 = matchFn("axxxayyyyyd",regex("(a.*){2}d")).captures; + assert(c2[0] == "axxxayyyyyd"); + assert(c2[1] == "ayyyyy"); + //issue 2108 + //greedy vs non-greedy + auto nogreed = regex(""); + assert(matchFn("texttext", nogreed).hit + == "text"); + auto greed = regex(""); + assert(matchFn("texttext", greed).hit + == "texttext"); + //issue 4574 + //empty successful match still advances the input + string[] pres, posts, hits; + foreach(m; matchFn("abcabc", regex("","g"))) { + pres ~= m.pre; + posts ~= m.post; + assert(m.hit.empty); + + } + auto heads = [ + "abcabc", + "abcab", + "abca", + "abc", + "ab", + "a", + "" + ]; + auto tails = [ + "abcabc", + "bcabc", + "cabc", + "abc", + "bc", + "c", + "" + ]; + assert(pres == array(retro(heads))); + assert(posts == tails); + //issue 6076 + //regression on .* + auto re = regex("c.*|d"); + auto m = matchFn("mm", re); + assert(!m); + debug(std_regex_test) writeln("!!! FReD REGRESSION test done "~matchFn.stringof~" !!!"); + auto rprealloc = regex(`((.){5}.{1,10}){5}`); + auto arr = array(repeat('0',100)); + auto m2 = matchFn(arr, rprealloc); + assert(m2); + assert(collectException( + regex(r"^(import|file|binary|config)\s+([^\(]+)\(?([^\)]*)\)?\s*$") + ) is null); + foreach(ch; [Escapables]) + { + assert(match(to!string(ch),regex(`[\`~ch~`]`))); + assert(!match(to!string(ch),regex(`[^\`~ch~`]`))); + assert(match(to!string(ch),regex(`[\`~ch~`-\`~ch~`]`))); + } + //bugzilla 7718 + string strcmd = "./myApp.rb -os OSX -path \"/GIT/Ruby Apps/sec\" -conf 'notimer'"; + auto reStrCmd = regex (`(".*")|('.*')`, "g"); + assert(equal(map!"a[0]"(matchFn(strcmd, reStrCmd)), + [`"/GIT/Ruby Apps/sec"`, `'notimer'`])); + } + test_body!bmatch(); + test_body!match(); +} + +// tests for replace +unittest +{ + void test(alias matchFn)() + { + import std.uni : toUpper; + + foreach(i, v; TypeTuple!(string, wstring, dstring)) + { + auto baz(Cap)(Cap m) + if (is(Cap == Captures!(Cap.String))) + { + return toUpper(m.hit); + } + alias String = v; + assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r")), to!String("c")) + == to!String("ack rapacity")); + assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r"), "g"), to!String("c")) + == to!String("ack capacity")); + assert(std.regex.replace!(matchFn)(to!String("noon"), regex(to!String("^n")), to!String("[$&]")) + == to!String("[n]oon")); + assert(std.regex.replace!(matchFn)(to!String("test1 test2"), regex(to!String(`\w+`),"g"), to!String("$`:$'")) + == to!String(": test2 test1 :")); + auto s = std.regex.replace!(baz!(Captures!(String)))(to!String("Strap a rocket engine on a chicken."), + regex(to!String("[ar]"), "g")); + assert(s == "StRAp A Rocket engine on A chicken."); + } + debug(std_regex_test) writeln("!!! Replace test done "~matchFn.stringof~" !!!"); + } + test!(bmatch)(); + test!(match)(); +} + +// tests for splitter +unittest +{ + auto s1 = ", abc, de, fg, hi, "; + auto sp1 = splitter(s1, regex(", *")); + auto w1 = ["", "abc", "de", "fg", "hi", ""]; + assert(equal(sp1, w1)); + + auto s2 = ", abc, de, fg, hi"; + auto sp2 = splitter(s2, regex(", *")); + auto w2 = ["", "abc", "de", "fg", "hi"]; + + uint cnt; + foreach(e; sp2) { + assert(w2[cnt++] == e); + } + assert(equal(sp2, w2)); +} + +unittest +{ + char[] s1 = ", abc, de, fg, hi, ".dup; + auto sp2 = splitter(s1, regex(", *")); +} + +unittest +{ + auto s1 = ", abc, de, fg, hi, "; + auto w1 = ["", "abc", "de", "fg", "hi", ""]; + assert(equal(split(s1, regex(", *")), w1[])); +} + +unittest +{ // bugzilla 7141 + string pattern = `[a\--b]`; + assert(match("-", pattern)); + assert(match("b", pattern)); + string pattern2 = `[&-z]`; + assert(match("b", pattern2)); +} +unittest +{//bugzilla 7111 + assert(match("", regex("^"))); +} +unittest +{//bugzilla 7300 + assert(!match("a"d, "aa"d)); +} + +unittest +{//bugzilla 7674 + assert("1234".replace(regex("^"), "$$") == "$1234"); + assert("hello?".replace(regex(r"\?", "g"), r"\?") == r"hello\?"); + assert("hello?".replace(regex(r"\?", "g"), r"\\?") != r"hello\?"); +} +unittest +{// bugzilla 7679 + foreach(S; TypeTuple!(string, wstring, dstring)) + { + enum re = ctRegex!(to!S(r"\.")); + auto str = to!S("a.b"); + assert(equal(std.regex.splitter(str, re), [to!S("a"), to!S("b")])); + assert(split(str, re) == [to!S("a"), to!S("b")]); + } +} +unittest +{//bugzilla 8203 + string data = " + NAME = XPAW01_STA:STATION + NAME = XPAW01_STA + "; + auto uniFileOld = data; + auto r = regex( + r"^NAME = (?P[a-zA-Z0-9_]+):*(?P[a-zA-Z0-9_]*)","gm"); + auto uniCapturesNew = match(uniFileOld, r); + for(int i = 0; i < 20; i++) + foreach (matchNew; uniCapturesNew) {} + //a second issue with same symptoms + auto r2 = regex(`([а-яА-Я\-_]+\s*)+(?<=[\s\.,\^])`); + match("аллея Театральная", r2); +} +unittest +{// bugzilla 8637 purity of enforce + auto m = match("hello world", regex("world")); + enforce(m); +} + +// bugzilla 8725 +unittest +{ + static italic = regex( r"\* + (?!\s+) + (.*?) + (?!\s+) + \*", "gx" ); + string input = "this * is* interesting, *very* interesting"; + assert(replace(input, italic, "$1") == + "this * is* interesting, very interesting"); +} + +// bugzilla 8349 +unittest +{ + enum peakRegexStr = r"\>(wgEncode.*Tfbs.*\.(?:narrow)|(?:broad)Peak.gz)"; + enum peakRegex = ctRegex!(peakRegexStr); + //note that the regex pattern itself is probably bogus + assert(match(r"\>wgEncode-blah-Tfbs.narrow", peakRegex)); +} + +// bugzilla 9211 +unittest +{ + auto rx_1 = regex(r"^(\w)*(\d)"); + auto m = match("1234", rx_1); + assert(equal(m.front, ["1234", "3", "4"])); + auto rx_2 = regex(r"^([0-9])*(\d)"); + auto m2 = match("1234", rx_2); + assert(equal(m2.front, ["1234", "3", "4"])); +} + +// bugzilla 9280 +unittest +{ + string tomatch = "a!b@c"; + static r = regex(r"^(?P.*?)!(?P.*?)@(?P.*?)$"); + auto nm = match(tomatch, r); + assert(nm); + auto c = nm.captures; + assert(c[1] == "a"); + assert(c["nick"] == "a"); +} + + +// bugzilla 9579 +unittest +{ + char[] input = ['a', 'b', 'c']; + string format = "($1)"; + // used to give a compile error: + auto re = regex(`(a)`, "g"); + auto r = replace(input, re, format); + assert(r == "(a)bc"); +} + +// bugzilla 9634 +unittest +{ + auto re = ctRegex!"(?:a+)"; + assert(match("aaaa", re).hit == "aaaa"); +} + +//bugzilla 10798 +unittest +{ + auto cr = ctRegex!("[abcd--c]*"); + auto m = "abc".match(cr); + assert(m); + assert(m.hit == "ab"); +} + +// bugzilla 10913 +unittest +{ + @system static string foo(const(char)[] s) + { + return s.dup; + } + @safe static string bar(const(char)[] s) + { + return s.dup; + } + () @system { + replace!((a) => foo(a.hit))("blah", regex(`a`)); + }(); + () @safe { + replace!((a) => bar(a.hit))("blah", regex(`a`)); + }(); +} + +// bugzilla 11262 +unittest +{ + enum reg = ctRegex!(r",", "g"); + auto str = "This,List"; + str = str.replace(reg, "-"); + assert(str == "This-List"); +} + +// bugzilla 11775 +unittest +{ + assert(collectException(regex("a{1,0}"))); +} + +// bugzilla 11839 +unittest +{ + assert(regex(`(?P\w+)`).namedCaptures.equal(["var1"])); + assert(collectException(regex(`(?P<1>\w+)`))); + assert(regex(`(?P\w+)`).namedCaptures.equal(["v1"])); + assert(regex(`(?P<__>\w+)`).namedCaptures.equal(["__"])); + assert(regex(`(?P<я>\w+)`).namedCaptures.equal(["я"])); +} + +// bugzilla 12076 +unittest +{ + auto RE = ctRegex!(r"(?abc)`); + assert(collectException("abc".matchFirst(r)["b"])); +} + +// bugzilla 12691 +unittest +{ + assert(bmatch("e@", "^([a-z]|)*$").empty); + assert(bmatch("e@", ctRegex!`^([a-z]|)*$`).empty); +} + +//bugzilla 12713 +unittest +{ + assertThrown(regex("[[a-z]([a-z]|(([[a-z])))")); +} + +//bugzilla 12747 +unittest +{ + assertThrown(regex(`^x(\1)`)); + assertThrown(regex(`^(x(\1))`)); + assertThrown(regex(`^((x)(?=\1))`)); +} diff --git a/std/regex/internal/thompson.d b/std/regex/internal/thompson.d new file mode 100644 index 00000000000..275e37df104 --- /dev/null +++ b/std/regex/internal/thompson.d @@ -0,0 +1,947 @@ +//Written in the D programming language +/* + Implementation of Thompson NFA std.regex engine. + Key point is evaluation of all possible threads (state) at each step + in a breadth-first manner, thereby geting some nice properties: + - looking at each character only once + - merging of equivalent threads, that gives matching process linear time complexity +*/ +module std.regex.internal.thompson; + +package(std.regex): + +import std.regex.internal.ir; +import std.range; + +//State of VM thread +struct Thread(DataIndex) +{ + Thread* next; //intrusive linked list + uint pc; + uint counter; //loop counter + uint uopCounter; //counts micro operations inside one macro instruction (e.g. BackRef) + Group!DataIndex[1] matches; +} + +//head-tail singly-linked list +struct ThreadList(DataIndex) +{ + Thread!DataIndex* tip = null, toe = null; + //add new thread to the start of list + void insertFront(Thread!DataIndex* t) + { + if(tip) + { + t.next = tip; + tip = t; + } + else + { + t.next = null; + tip = toe = t; + } + } + //add new thread to the end of list + void insertBack(Thread!DataIndex* t) + { + if(toe) + { + toe.next = t; + toe = t; + } + else + tip = toe = t; + toe.next = null; + } + //move head element out of list + Thread!DataIndex* fetch() + { + auto t = tip; + if(tip == toe) + tip = toe = null; + else + tip = tip.next; + return t; + } + //non-destructive iteration of ThreadList + struct ThreadRange + { + const(Thread!DataIndex)* ct; + this(ThreadList tlist){ ct = tlist.tip; } + @property bool empty(){ return ct is null; } + @property const(Thread!DataIndex)* front(){ return ct; } + @property popFront() + { + assert(ct); + ct = ct.next; + } + } + @property bool empty() + { + return tip == null; + } + ThreadRange opSlice() + { + return ThreadRange(this); + } +} + +/+ + Thomspon matcher does all matching in lockstep, + never looking at the same char twice ++/ +@trusted struct ThompsonMatcher(Char, Stream = Input!Char) + if(is(Char : dchar)) +{ + alias DataIndex = Stream.DataIndex; + Thread!DataIndex* freelist; + ThreadList!DataIndex clist, nlist; + DataIndex[] merge; + Group!DataIndex[] backrefed; + Regex!Char re; //regex program + Stream s; + dchar front; + DataIndex index; + DataIndex genCounter; //merge trace counter, goes up on every dchar + size_t[size_t] subCounters; //a table of gen counter per sub-engine: PC -> counter + size_t threadSize; + bool matched; + bool exhausted; + static if(__traits(hasMember,Stream, "search")) + { + enum kicked = true; + } + else + enum kicked = false; + + static size_t getThreadSize(const ref Regex!Char re) + { + return re.ngroup + ? (Thread!DataIndex).sizeof + (re.ngroup-1)*(Group!DataIndex).sizeof + : (Thread!DataIndex).sizeof - (Group!DataIndex).sizeof; + } + + static size_t initialMemory(const ref Regex!Char re) + { + return getThreadSize(re)*re.threadCount + re.hotspotTableSize*size_t.sizeof; + } + + //true if it's start of input + @property bool atStart(){ return index == 0; } + + //true if it's end of input + @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } + + bool next() + { + if(!s.nextChar(front, index)) + { + index = s.lastIndex; + return false; + } + return true; + } + + static if(kicked) + { + bool search() + { + + if(!s.search(re.kickstart, front, index)) + { + index = s.lastIndex; + return false; + } + return true; + } + } + + void initExternalMemory(void[] memory) + { + threadSize = getThreadSize(re); + prepareFreeList(re.threadCount, memory); + if(re.hotspotTableSize) + { + merge = arrayInChunk!(DataIndex)(re.hotspotTableSize, memory); + merge[] = 0; + } + } + + this()(Regex!Char program, Stream stream, void[] memory) + { + re = program; + s = stream; + initExternalMemory(memory); + genCounter = 0; + } + + this(S)(ref ThompsonMatcher!(Char,S) matcher, Bytecode[] piece, Stream stream) + { + s = stream; + re = matcher.re; + re.ir = piece; + threadSize = matcher.threadSize; + merge = matcher.merge; + freelist = matcher.freelist; + front = matcher.front; + index = matcher.index; + } + + auto fwdMatcher()(Bytecode[] piece, size_t counter) + { + auto m = ThompsonMatcher!(Char, Stream)(this, piece, s); + m.genCounter = counter; + return m; + } + + auto bwdMatcher()(Bytecode[] piece, size_t counter) + { + alias BackLooper = typeof(s.loopBack(index)); + auto m = ThompsonMatcher!(Char, BackLooper)(this, piece, s.loopBack(index)); + m.genCounter = counter; + m.next(); + return m; + } + + auto dupTo(void[] memory) + { + typeof(this) tmp = this;//bitblit + tmp.initExternalMemory(memory); + tmp.genCounter = 0; + return tmp; + } + + enum MatchResult{ + NoMatch, + PartialMatch, + Match, + } + + bool match(Group!DataIndex[] matches) + { + debug(std_regex_matcher) + writeln("------------------------------------------"); + if(exhausted) + { + return false; + } + if(re.flags & RegexInfo.oneShot) + { + next(); + exhausted = true; + return matchOneShot(matches)==MatchResult.Match; + } + static if(kicked) + if(!re.kickstart.empty) + return matchImpl!(true)(matches); + return matchImpl!(false)(matches); + } + + //match the input and fill matches + bool matchImpl(bool withSearch)(Group!DataIndex[] matches) + { + if(!matched && clist.empty) + { + static if(withSearch) + search(); + else + next(); + } + else//char in question is fetched in prev call to match + { + matched = false; + } + + if(!atEnd)//if no char + for(;;) + { + genCounter++; + debug(std_regex_matcher) + { + writefln("Threaded matching threads at %s", s[index..s.lastIndex]); + foreach(t; clist[]) + { + assert(t); + writef("pc=%s ",t.pc); + write(t.matches); + writeln(); + } + } + for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) + { + eval!true(t, matches); + } + if(!matched)//if we already have match no need to push the engine + eval!true(createStart(index), matches);//new thread staring at this position + else if(nlist.empty) + { + debug(std_regex_matcher) writeln("Stopped matching before consuming full input"); + break;//not a partial match for sure + } + clist = nlist; + nlist = (ThreadList!DataIndex).init; + if(clist.tip is null) + { + static if(withSearch) + { + if(!search()) + break; + } + else + { + if(!next()) + break; + } + } + else if(!next()) + { + if (!atEnd) return false; + exhausted = true; + break; + } + } + + genCounter++; //increment also on each end + debug(std_regex_matcher) writefln("Threaded matching threads at end"); + //try out all zero-width posibilities + for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) + { + eval!false(t, matches); + } + if(!matched) + eval!false(createStart(index), matches);//new thread starting at end of input + if(matched) + {//in case NFA found match along the way + //and last possible longer alternative ultimately failed + s.reset(matches[0].end);//reset to last successful match + next();//and reload front character + //--- here the exact state of stream was restored --- + exhausted = atEnd || !(re.flags & RegexOption.global); + //+ empty match advances the input + if(!exhausted && matches[0].begin == matches[0].end) + next(); + } + return matched; + } + + /+ + handle succesful threads + +/ + void finish(const(Thread!DataIndex)* t, Group!DataIndex[] matches) + { + matches.ptr[0..re.ngroup] = t.matches.ptr[0..re.ngroup]; + debug(std_regex_matcher) + { + writef("FOUND pc=%s prog_len=%s", + t.pc, re.ir.length); + if(!matches.empty) + writefln(": %s..%s", matches[0].begin, matches[0].end); + foreach(v; matches) + writefln("%d .. %d", v.begin, v.end); + } + matched = true; + } + + /+ + match thread against codepoint, cutting trough all 0-width instructions + and taking care of control flow, then add it to nlist + +/ + void eval(bool withInput)(Thread!DataIndex* t, Group!DataIndex[] matches) + { + ThreadList!DataIndex worklist; + debug(std_regex_matcher) writeln("---- Evaluating thread"); + for(;;) + { + debug(std_regex_matcher) + { + writef("\tpc=%s [", t.pc); + foreach(x; worklist[]) + writef(" %s ", x.pc); + writeln("]"); + } + switch(re.ir[t.pc].code) + { + case IR.End: + finish(t, matches); + matches[0].end = index; //fix endpoint of the whole match + recycle(t); + //cut off low priority threads + recycle(clist); + recycle(worklist); + debug(std_regex_matcher) writeln("Finished thread ", matches); + return; + case IR.Wordboundary: + dchar back; + DataIndex bi; + //at start & end of input + if(atStart && wordTrie[front]) + { + t.pc += IRL!(IR.Wordboundary); + break; + } + else if(atEnd && s.loopBack(index).nextChar(back, bi) + && wordTrie[back]) + { + t.pc += IRL!(IR.Wordboundary); + break; + } + else if(s.loopBack(index).nextChar(back, bi)) + { + bool af = wordTrie[front]; + bool ab = wordTrie[back]; + if(af ^ ab) + { + t.pc += IRL!(IR.Wordboundary); + break; + } + } + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + case IR.Notwordboundary: + dchar back; + DataIndex bi; + //at start & end of input + if(atStart && wordTrie[front]) + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + else if(atEnd && s.loopBack(index).nextChar(back, bi) + && wordTrie[back]) + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + else if(s.loopBack(index).nextChar(back, bi)) + { + bool af = wordTrie[front]; + bool ab = wordTrie[back] != 0; + if(af ^ ab) + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + } + t.pc += IRL!(IR.Wordboundary); + break; + case IR.Bol: + dchar back; + DataIndex bi; + if(atStart + ||( (re.flags & RegexOption.multiline) + && s.loopBack(index).nextChar(back,bi) + && startOfLine(back, front == '\n'))) + { + t.pc += IRL!(IR.Bol); + } + else + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + } + break; + case IR.Eol: + debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index..s.lastIndex]); + dchar back; + DataIndex bi; + //no matching inside \r\n + if(atEnd || ((re.flags & RegexOption.multiline) + && endOfLine(front, s.loopBack(index).nextChar(back, bi) + && back == '\r'))) + { + t.pc += IRL!(IR.Eol); + } + else + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + } + break; + case IR.InfiniteStart, IR.InfiniteQStart: + t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart); + goto case IR.InfiniteEnd; //both Q and non-Q + case IR.RepeatStart, IR.RepeatQStart: + t.pc += re.ir[t.pc].data + IRL!(IR.RepeatStart); + goto case IR.RepeatEnd; //both Q and non-Q + case IR.RepeatEnd: + case IR.RepeatQEnd: + //len, step, min, max + uint len = re.ir[t.pc].data; + uint step = re.ir[t.pc+2].raw; + uint min = re.ir[t.pc+3].raw; + if(t.counter < min) + { + t.counter += step; + t.pc -= len; + break; + } + if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) + { + debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; + } + else + { + debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + uint max = re.ir[t.pc+4].raw; + if(t.counter < max) + { + if(re.ir[t.pc].code == IR.RepeatEnd) + { + //queue out-of-loop thread + worklist.insertFront(fork(t, t.pc + IRL!(IR.RepeatEnd), t.counter % step)); + t.counter += step; + t.pc -= len; + } + else + { + //queue into-loop thread + worklist.insertFront(fork(t, t.pc - len, t.counter + step)); + t.counter %= step; + t.pc += IRL!(IR.RepeatEnd); + } + } + else + { + t.counter %= step; + t.pc += IRL!(IR.RepeatEnd); + } + break; + case IR.InfiniteEnd: + case IR.InfiniteQEnd: + if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) + { + debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; + } + else + { + debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + uint len = re.ir[t.pc].data; + uint pc1, pc2; //branches to take in priority order + if(re.ir[t.pc].code == IR.InfiniteEnd) + { + pc1 = t.pc - len; + pc2 = t.pc + IRL!(IR.InfiniteEnd); + } + else + { + pc1 = t.pc + IRL!(IR.InfiniteEnd); + pc2 = t.pc - len; + } + static if(withInput) + { + int test = quickTestFwd(pc1, front, re); + if(test >= 0) + { + worklist.insertFront(fork(t, pc2, t.counter)); + t.pc = pc1; + } + else + t.pc = pc2; + } + else + { + worklist.insertFront(fork(t, pc2, t.counter)); + t.pc = pc1; + } + break; + case IR.OrEnd: + if(merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) + { + debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", + t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] ); + merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; + t.pc += IRL!(IR.OrEnd); + } + else + { + debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", + t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] ); + recycle(t); + t = worklist.fetch(); + if(!t) + return; + } + break; + case IR.OrStart: + t.pc += IRL!(IR.OrStart); + goto case; + case IR.Option: + uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option); + //queue next Option + if(re.ir[next].code == IR.Option) + { + worklist.insertFront(fork(t, next, t.counter)); + } + t.pc += IRL!(IR.Option); + break; + case IR.GotoEndOr: + t.pc = t.pc + re.ir[t.pc].data + IRL!(IR.GotoEndOr); + goto case IR.OrEnd; + case IR.GroupStart: + uint n = re.ir[t.pc].data; + t.matches.ptr[n].begin = index; + t.pc += IRL!(IR.GroupStart); + break; + case IR.GroupEnd: + uint n = re.ir[t.pc].data; + t.matches.ptr[n].end = index; + t.pc += IRL!(IR.GroupEnd); + break; + case IR.Backref: + uint n = re.ir[t.pc].data; + Group!DataIndex* source = re.ir[t.pc].localRef ? t.matches.ptr : backrefed.ptr; + assert(source); + if(source[n].begin == source[n].end)//zero-width Backref! + { + t.pc += IRL!(IR.Backref); + } + else static if(withInput) + { + size_t idx = source[n].begin + t.uopCounter; + size_t end = source[n].end; + if(s[idx..end].front == front) + { + t.uopCounter += std.utf.stride(s[idx..end], 0); + if(t.uopCounter + source[n].begin == source[n].end) + {//last codepoint + t.pc += IRL!(IR.Backref); + t.uopCounter = 0; + } + nlist.insertBack(t); + } + else + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + else + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + break; + case IR.LookbehindStart: + case IR.NeglookbehindStart: + uint len = re.ir[t.pc].data; + uint ms = re.ir[t.pc + 1].raw, me = re.ir[t.pc + 2].raw; + uint end = t.pc + len + IRL!(IR.LookbehindEnd) + IRL!(IR.LookbehindStart); + bool positive = re.ir[t.pc].code == IR.LookbehindStart; + static if(Stream.isLoopback) + auto matcher = fwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); + else + auto matcher = bwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); + matcher.re.ngroup = me - ms; + matcher.backrefed = backrefed.empty ? t.matches : backrefed; + //backMatch + auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookbehindStart)); + freelist = matcher.freelist; + subCounters[t.pc] = matcher.genCounter; + if((mRes == MatchResult.Match) ^ positive) + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + else + t.pc = end; + break; + case IR.LookaheadStart: + case IR.NeglookaheadStart: + auto save = index; + uint len = re.ir[t.pc].data; + uint ms = re.ir[t.pc+1].raw, me = re.ir[t.pc+2].raw; + uint end = t.pc+len+IRL!(IR.LookaheadEnd)+IRL!(IR.LookaheadStart); + bool positive = re.ir[t.pc].code == IR.LookaheadStart; + static if(Stream.isLoopback) + auto matcher = bwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); + else + auto matcher = fwdMatcher(re.ir[t.pc .. end], subCounters.get(t.pc, 0)); + matcher.re.ngroup = me - ms; + matcher.backrefed = backrefed.empty ? t.matches : backrefed; + auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookaheadStart)); + freelist = matcher.freelist; + subCounters[t.pc] = matcher.genCounter; + s.reset(index); + next(); + if((mRes == MatchResult.Match) ^ positive) + { + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + } + else + t.pc = end; + break; + case IR.LookaheadEnd: + case IR.NeglookaheadEnd: + case IR.LookbehindEnd: + case IR.NeglookbehindEnd: + finish(t, matches.ptr[0 .. re.ngroup]); + recycle(t); + //cut off low priority threads + recycle(clist); + recycle(worklist); + return; + case IR.Nop: + t.pc += IRL!(IR.Nop); + break; + + static if(withInput) + { + case IR.OrChar: + uint len = re.ir[t.pc].sequence; + uint end = t.pc + len; + static assert(IRL!(IR.OrChar) == 1); + for(; t.pc < end; t.pc++) + if(re.ir[t.pc].data == front) + break; + if(t.pc != end) + { + t.pc = end; + nlist.insertBack(t); + } + else + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + case IR.Char: + if(front == re.ir[t.pc].data) + { + t.pc += IRL!(IR.Char); + nlist.insertBack(t); + } + else + recycle(t); + t = worklist.fetch(); + if(!t) + return; + break; + case IR.Any: + t.pc += IRL!(IR.Any); + if(!(re.flags & RegexOption.singleline) + && (front == '\r' || front == '\n')) + recycle(t); + else + nlist.insertBack(t); + t = worklist.fetch(); + if(!t) + return; + break; + case IR.CodepointSet: + if(re.charsets[re.ir[t.pc].data].scanFor(front)) + { + t.pc += IRL!(IR.CodepointSet); + nlist.insertBack(t); + } + else + { + recycle(t); + } + t = worklist.fetch(); + if(!t) + return; + break; + case IR.Trie: + if(re.tries[re.ir[t.pc].data][front]) + { + t.pc += IRL!(IR.Trie); + nlist.insertBack(t); + } + else + { + recycle(t); + } + t = worklist.fetch(); + if(!t) + return; + break; + default: + assert(0, "Unrecognized instruction " ~ re.ir[t.pc].mnemonic); + } + else + { + default: + recycle(t); + t = worklist.fetch(); + if(!t) + return; + } + } + } + + } + enum uint RestartPc = uint.max; + //match the input, evaluating IR without searching + MatchResult matchOneShot(Group!DataIndex[] matches, uint startPc = 0) + { + debug(std_regex_matcher) + { + writefln("---------------single shot match ----------------- "); + } + alias evalFn = eval; + assert(clist == (ThreadList!DataIndex).init || startPc == RestartPc); // incorrect after a partial match + assert(nlist == (ThreadList!DataIndex).init || startPc == RestartPc); + if(!atEnd)//if no char + { + debug(std_regex_matcher) + { + writefln("-- Threaded matching threads at %s", s[index..s.lastIndex]); + } + if(startPc!=RestartPc) + { + auto startT = createStart(index, startPc); + genCounter++; + evalFn!true(startT, matches); + } + for(;;) + { + debug(std_regex_matcher) writeln("\n-- Started iteration of main cycle"); + genCounter++; + debug(std_regex_matcher) + { + foreach(t; clist[]) + { + assert(t); + } + } + for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) + { + evalFn!true(t, matches); + } + if(nlist.empty) + { + debug(std_regex_matcher) writeln("Stopped matching before consuming full input"); + break;//not a partial match for sure + } + clist = nlist; + nlist = (ThreadList!DataIndex).init; + if(!next()) + { + if (!atEnd) return MatchResult.PartialMatch; + break; + } + debug(std_regex_matcher) writeln("-- Ended iteration of main cycle\n"); + } + } + genCounter++; //increment also on each end + debug(std_regex_matcher) writefln("-- Matching threads at end"); + //try out all zero-width posibilities + for(Thread!DataIndex* t = clist.fetch(); t; t = clist.fetch()) + { + evalFn!false(t, matches); + } + if(!matched) + evalFn!false(createStart(index, startPc), matches); + + return (matched?MatchResult.Match:MatchResult.NoMatch); + } + + //get a dirty recycled Thread + Thread!DataIndex* allocate() + { + assert(freelist, "not enough preallocated memory"); + Thread!DataIndex* t = freelist; + freelist = freelist.next; + return t; + } + + //link memory into a free list of Threads + void prepareFreeList(size_t size, ref void[] memory) + { + void[] mem = memory[0 .. threadSize*size]; + memory = memory[threadSize * size .. $]; + freelist = cast(Thread!DataIndex*)&mem[0]; + size_t i; + for(i = threadSize; i < threadSize*size; i += threadSize) + (cast(Thread!DataIndex*)&mem[i-threadSize]).next = cast(Thread!DataIndex*)&mem[i]; + (cast(Thread!DataIndex*)&mem[i-threadSize]).next = null; + } + + //dispose a thread + void recycle(Thread!DataIndex* t) + { + t.next = freelist; + freelist = t; + } + + //dispose list of threads + void recycle(ref ThreadList!DataIndex list) + { + auto t = list.tip; + while(t) + { + auto next = t.next; + recycle(t); + t = next; + } + list = list.init; + } + + //creates a copy of master thread with given pc + Thread!DataIndex* fork(Thread!DataIndex* master, uint pc, uint counter) + { + auto t = allocate(); + t.matches.ptr[0..re.ngroup] = master.matches.ptr[0..re.ngroup]; + t.pc = pc; + t.counter = counter; + t.uopCounter = 0; + return t; + } + + //creates a start thread + Thread!DataIndex* createStart(DataIndex index, uint pc = 0) + { + auto t = allocate(); + t.matches.ptr[0..re.ngroup] = (Group!DataIndex).init; + t.matches[0].begin = index; + t.pc = pc; + t.counter = 0; + t.uopCounter = 0; + return t; + } +} diff --git a/std/regex/package.d b/std/regex/package.d new file mode 100644 index 00000000000..f421919cda2 --- /dev/null +++ b/std/regex/package.d @@ -0,0 +1,1428 @@ +/++ + $(LUCKY Regular expressions) are a commonly used method of pattern matching + on strings, with $(I regex) being a catchy word for a pattern in this domain + specific language. Typical problems usually solved by regular expressions + include validation of user input and the ubiquitous find & replace + in text processing utilities. + + $(SECTION Synopsis) + --- + import std.regex; + import std.stdio; + void main() + { + // Print out all possible dd/mm/yy(yy) dates found in user input. + auto r = regex(r"\b[0-9][0-9]?/[0-9][0-9]?/[0-9][0-9](?:[0-9][0-9])?\b"); + foreach(line; stdin.byLine) + { + // matchAll() returns a range that can be iterated + // to get all subsequent matches. + foreach(c; matchAll(line, r)) + writeln(c.hit); + } + } + ... + + // Create a static regex at compile-time, which contains fast native code. + auto ctr = ctRegex!(`^.*/([^/]+)/?$`); + + // It works just like a normal regex: + auto c2 = matchFirst("foo/bar", ctr); // First match found here, if any + assert(!c2.empty); // Be sure to check if there is a match before examining contents! + assert(c2[1] == "bar"); // Captures is a range of submatches: 0 = full match. + + ... + + // The result of the $(D matchAll) is directly testable with if/assert/while. + // e.g. test if a string consists of letters: + assert(matchFirst("Letter", `^\p{L}+$`)); + + + --- + $(SECTION Syntax and general information) + The general usage guideline is to keep regex complexity on the side of simplicity, + as its capabilities reside in purely character-level manipulation. + As such it's ill-suited for tasks involving higher level invariants + like matching an integer number $(U bounded) in an [a,b] interval. + Checks of this sort of are better addressed by additional post-processing. + + The basic syntax shouldn't surprise experienced users of regular expressions. + For an introduction to $(D std.regex) see a + $(WEB dlang.org/regular-expression.html, short tour) of the module API + and its abilities. + + There are other web resources on regular expressions to help newcomers, + and a good $(WEB www.regular-expressions.info, reference with tutorial) + can easily be found. + + This library uses a remarkably common ECMAScript syntax flavor + with the following extensions: + $(UL + $(LI Named subexpressions, with Python syntax. ) + $(LI Unicode properties such as Scripts, Blocks and common binary properties e.g Alphabetic, White_Space, Hex_Digit etc.) + $(LI Arbitrary length and complexity lookbehind, including lookahead in lookbehind and vise-versa.) + ) + + $(REG_START Pattern syntax ) + $(I std.regex operates on codepoint level, + 'character' in this table denotes a single Unicode codepoint.) + $(REG_TABLE + $(REG_TITLE Pattern element, Semantics ) + $(REG_TITLE Atoms, Match single characters ) + $(REG_ROW any character except [{|*+?()^$, Matches the character itself. ) + $(REG_ROW ., In single line mode matches any character. + Otherwise it matches any character except '\n' and '\r'. ) + $(REG_ROW [class], Matches a single character + that belongs to this character class. ) + $(REG_ROW [^class], Matches a single character that + does $(U not) belong to this character class.) + $(REG_ROW \cC, Matches the control character corresponding to letter C) + $(REG_ROW \xXX, Matches a character with hexadecimal value of XX. ) + $(REG_ROW \uXXXX, Matches a character with hexadecimal value of XXXX. ) + $(REG_ROW \U00YYYYYY, Matches a character with hexadecimal value of YYYYYY. ) + $(REG_ROW \f, Matches a formfeed character. ) + $(REG_ROW \n, Matches a linefeed character. ) + $(REG_ROW \r, Matches a carriage return character. ) + $(REG_ROW \t, Matches a tab character. ) + $(REG_ROW \v, Matches a vertical tab character. ) + $(REG_ROW \d, Matches any Unicode digit. ) + $(REG_ROW \D, Matches any character except Unicode digits. ) + $(REG_ROW \w, Matches any word character (note: this includes numbers).) + $(REG_ROW \W, Matches any non-word character.) + $(REG_ROW \s, Matches whitespace, same as \p{White_Space}.) + $(REG_ROW \S, Matches any character except those recognized as $(I \s ). ) + $(REG_ROW \\, Matches \ character. ) + $(REG_ROW \c where c is one of [|*+?(), Matches the character c itself. ) + $(REG_ROW \p{PropertyName}, Matches a character that belongs + to the Unicode PropertyName set. + Single letter abbreviations can be used without surrounding {,}. ) + $(REG_ROW \P{PropertyName}, Matches a character that does not belong + to the Unicode PropertyName set. + Single letter abbreviations can be used without surrounding {,}. ) + $(REG_ROW \p{InBasicLatin}, Matches any character that is part of + the BasicLatin Unicode $(U block).) + $(REG_ROW \P{InBasicLatin}, Matches any character except ones in + the BasicLatin Unicode $(U block).) + $(REG_ROW \p{Cyrillic}, Matches any character that is part of + Cyrillic $(U script).) + $(REG_ROW \P{Cyrillic}, Matches any character except ones in + Cyrillic $(U script).) + $(REG_TITLE Quantifiers, Specify repetition of other elements) + $(REG_ROW *, Matches previous character/subexpression 0 or more times. + Greedy version - tries as many times as possible.) + $(REG_ROW *?, Matches previous character/subexpression 0 or more times. + Lazy version - stops as early as possible.) + $(REG_ROW +, Matches previous character/subexpression 1 or more times. + Greedy version - tries as many times as possible.) + $(REG_ROW +?, Matches previous character/subexpression 1 or more times. + Lazy version - stops as early as possible.) + $(REG_ROW {n}, Matches previous character/subexpression exactly n times. ) + $(REG_ROW {n,}, Matches previous character/subexpression n times or more. + Greedy version - tries as many times as possible. ) + $(REG_ROW {n,}?, Matches previous character/subexpression n times or more. + Lazy version - stops as early as possible.) + $(REG_ROW {n,m}, Matches previous character/subexpression n to m times. + Greedy version - tries as many times as possible, but no more than m times. ) + $(REG_ROW {n,m}?, Matches previous character/subexpression n to m times. + Lazy version - stops as early as possible, but no less then n times.) + $(REG_TITLE Other, Subexpressions & alternations ) + $(REG_ROW (regex), Matches subexpression regex, + saving matched portion of text for later retrieval. ) + $(REG_ROW (?:regex), Matches subexpression regex, + $(U not) saving matched portion of text. Useful to speed up matching. ) + $(REG_ROW A|B, Matches subexpression A, or failing that, matches B. ) + $(REG_ROW (?P<name>regex), Matches named subexpression + regex labeling it with name 'name'. + When referring to a matched portion of text, + names work like aliases in addition to direct numbers. + ) + $(REG_TITLE Assertions, Match position rather than character ) + $(REG_ROW ^, Matches at the begining of input or line (in multiline mode).) + $(REG_ROW $, Matches at the end of input or line (in multiline mode). ) + $(REG_ROW \b, Matches at word boundary. ) + $(REG_ROW \B, Matches when $(U not) at word boundary. ) + $(REG_ROW (?=regex), Zero-width lookahead assertion. + Matches at a point where the subexpression + regex could be matched starting from the current position. + ) + $(REG_ROW (?!regex), Zero-width negative lookahead assertion. + Matches at a point where the subexpression + regex could $(U not) be matched starting from the current position. + ) + $(REG_ROW (?<=regex), Zero-width lookbehind assertion. Matches at a point + where the subexpression regex could be matched ending + at the current position (matching goes backwards). + ) + $(REG_ROW (? $0 + REG_START =

$0

+ SECTION =

$0

+ S_LINK = $+ + +/ +module std.regex; + +import std.regex.internal.ir; +import std.regex.internal.thompson; //TODO: get rid of this dependency +import std.exception, std.traits, std.range; + +/++ + $(D Regex) object holds regular expression pattern in compiled form. + + Instances of this object are constructed via calls to $(D regex). + This is an intended form for caching and storage of frequently + used regular expressions. + + Examples: + + Test if this object doesn't contain any compiled pattern. + --- + Regex!char r; + assert(r.empty); + r = regex(""); // Note: "" is a valid regex pattern. + assert(!r.empty); + --- + + Getting a range of all the named captures in the regex. + ---- + import std.range; + import std.algorithm; + + auto re = regex(`(?P\w+) = (?P\d+)`); + auto nc = re.namedCaptures; + static assert(isRandomAccessRange!(typeof(nc))); + assert(!nc.empty); + assert(nc.length == 2); + assert(nc.equal(["name", "var"])); + assert(nc[0] == "name"); + assert(nc[1..$].equal(["var"])); + ---- ++/ +public alias Regex(Char) = std.regex.internal.ir.Regex!(Char); + +/++ + A $(D StaticRegex) is $(D Regex) object that contains D code specially + generated at compile-time to speed up matching. + + Implicitly convertible to normal $(D Regex), + however doing so will result in losing this additional capability. ++/ +public alias StaticRegex(Char) = std.regex.internal.ir.StaticRegex!(Char); + +/++ + Compile regular expression pattern for the later execution. + Returns: $(D Regex) object that works on inputs having + the same character width as $(D pattern). + + Params: + pattern = Regular expression + flags = The _attributes (g, i, m and x accepted) + + Throws: $(D RegexException) if there were any errors during compilation. ++/ +@trusted public auto regex(S)(S pattern, const(char)[] flags="") + if(isSomeString!(S)) +{ + import std.functional; + enum cacheSize = 8; //TODO: invent nice interface to control regex caching + if(__ctfe) + return regexImpl(pattern, flags); + return memoize!(regexImpl!S, cacheSize)(pattern, flags); +} + +public auto regexImpl(S)(S pattern, const(char)[] flags="") + if(isSomeString!(S)) +{ + import std.regex.internal.parser; + auto parser = Parser!(Unqual!(typeof(pattern)))(pattern, flags); + auto r = parser.program; + return r; +} + + +template ctRegexImpl(alias pattern, string flags=[]) +{ + import std.regex.internal.parser, std.regex.internal.backtracking; + enum r = regex(pattern, flags); + alias Char = BasicElementOf!(typeof(pattern)); + enum source = ctGenRegExCode(r); + alias Matcher = BacktrackingMatcher!(true); + @trusted bool func(ref Matcher!Char matcher) + { + debug(std_regex_ctr) pragma(msg, source); + mixin(source); + } + enum nr = StaticRegex!Char(r, &func); +} + +/++ + Compile regular expression using CTFE + and generate optimized native machine code for matching it. + + Returns: StaticRegex object for faster matching. + + Params: + pattern = Regular expression + flags = The _attributes (g, i, m and x accepted) ++/ +public enum ctRegex(alias pattern, alias flags=[]) = ctRegexImpl!(pattern, flags).nr; + +enum isRegexFor(RegEx, R) = is(RegEx == Regex!(BasicElementOf!R)) + || is(RegEx == StaticRegex!(BasicElementOf!R)); + + +/++ + $(D Captures) object contains submatches captured during a call + to $(D match) or iteration over $(D RegexMatch) range. + + First element of range is the whole match. ++/ +@trusted public struct Captures(R, DIndex = size_t) + if(isSomeString!R) +{//@trusted because of union inside + alias DataIndex = DIndex; + alias String = R; +private: + import std.conv; + R _input; + bool _empty; + enum smallString = 3; + union + { + Group!DataIndex[] big_matches; + Group!DataIndex[smallString] small_matches; + } + uint _f, _b; + uint _ngroup; + NamedGroup[] _names; + + this()(R input, uint ngroups, NamedGroup[] named) + { + _input = input; + _ngroup = ngroups; + _names = named; + newMatches(); + _b = _ngroup; + _f = 0; + } + + this(alias Engine)(ref RegexMatch!(R,Engine) rmatch) + { + _input = rmatch._input; + _ngroup = rmatch._engine.re.ngroup; + _names = rmatch._engine.re.dict; + newMatches(); + _b = _ngroup; + _f = 0; + } + + @property Group!DataIndex[] matches() + { + return _ngroup > smallString ? big_matches : small_matches[0 .. _ngroup]; + } + + void newMatches() + { + if(_ngroup > smallString) + big_matches = new Group!DataIndex[_ngroup]; + } + +public: + ///Slice of input prior to the match. + @property R pre() + { + return _empty ? _input[] : _input[0 .. matches[0].begin]; + } + + ///Slice of input immediately after the match. + @property R post() + { + return _empty ? _input[] : _input[matches[0].end .. $]; + } + + ///Slice of matched portion of input. + @property R hit() + { + assert(!_empty); + return _input[matches[0].begin .. matches[0].end]; + } + + ///Range interface. + @property R front() + { + assert(!empty); + return _input[matches[_f].begin .. matches[_f].end]; + } + + ///ditto + @property R back() + { + assert(!empty); + return _input[matches[_b - 1].begin .. matches[_b - 1].end]; + } + + ///ditto + void popFront() + { + assert(!empty); + ++_f; + } + + ///ditto + void popBack() + { + assert(!empty); + --_b; + } + + ///ditto + @property bool empty() const { return _empty || _f >= _b; } + + ///ditto + R opIndex()(size_t i) /*const*/ //@@@BUG@@@ + { + assert(_f + i < _b,text("requested submatch number ", i," is out of range")); + assert(matches[_f + i].begin <= matches[_f + i].end, + text("wrong match: ", matches[_f + i].begin, "..", matches[_f + i].end)); + return _input[matches[_f + i].begin .. matches[_f + i].end]; + } + + /++ + Explicit cast to bool. + Useful as a shorthand for !(x.empty) in if and assert statements. + + --- + import std.regex; + + assert(!matchFirst("nothing", "something")); + --- + +/ + + @safe bool opCast(T:bool)() const nothrow { return !empty; } + + /++ + Lookup named submatch. + + --- + import std.regex; + import std.range; + + auto c = matchFirst("a = 42;", regex(`(?P\w+)\s*=\s*(?P\d+);`)); + assert(c["var"] == "a"); + assert(c["value"] == "42"); + popFrontN(c, 2); + //named groups are unaffected by range primitives + assert(c["var"] =="a"); + assert(c.front == "42"); + ---- + +/ + R opIndex(String)(String i) /*const*/ //@@@BUG@@@ + if(isSomeString!String) + { + size_t index = lookupNamedGroup(_names, i); + return _input[matches[index].begin .. matches[index].end]; + } + + ///Number of matches in this object. + @property size_t length() const { return _empty ? 0 : _b - _f; } + + ///A hook for compatibility with original std.regex. + @property ref captures(){ return this; } +} + +/// +unittest +{ + auto c = matchFirst("@abc#", regex(`(\w)(\w)(\w)`)); + assert(c.pre == "@"); // Part of input preceding match + assert(c.post == "#"); // Immediately after match + assert(c.hit == c[0] && c.hit == "abc"); // The whole match + assert(c[2] == "b"); + assert(c.front == "abc"); + c.popFront(); + assert(c.front == "a"); + assert(c.back == "c"); + c.popBack(); + assert(c.back == "b"); + popFrontN(c, 2); + assert(c.empty); + + assert(!matchFirst("nothing", "something")); +} + +/++ + A regex engine state, as returned by $(D match) family of functions. + + Effectively it's a forward range of Captures!R, produced + by lazily searching for matches in a given input. + + $(D alias Engine) specifies an engine type to use during matching, + and is automatically deduced in a call to $(D match)/$(D bmatch). ++/ +@trusted public struct RegexMatch(R, alias Engine = ThompsonMatcher) + if(isSomeString!R) +{ +private: + import core.stdc.stdlib; + alias Char = BasicElementOf!R; + alias EngineType = Engine!Char; + EngineType _engine; + R _input; + Captures!(R,EngineType.DataIndex) _captures; + void[] _memory;//is ref-counted + + this(RegEx)(R input, RegEx prog) + { + _input = input; + immutable size = EngineType.initialMemory(prog)+size_t.sizeof; + _memory = (enforce(malloc(size))[0..size]); + scope(failure) free(_memory.ptr); + *cast(size_t*)_memory.ptr = 1; + _engine = EngineType(prog, Input!Char(input), _memory[size_t.sizeof..$]); + static if(is(RegEx == StaticRegex!(BasicElementOf!R))) + _engine.nativeFn = prog.nativeFn; + _captures = Captures!(R,EngineType.DataIndex)(this); + _captures._empty = !_engine.match(_captures.matches); + debug(std_regex_allocation) writefln("RefCount (ctor): %x %d", _memory.ptr, counter); + } + + @property ref size_t counter(){ return *cast(size_t*)_memory.ptr; } +public: + this(this) + { + if(_memory.ptr) + { + ++counter; + debug(std_regex_allocation) writefln("RefCount (postblit): %x %d", + _memory.ptr, *cast(size_t*)_memory.ptr); + } + } + + ~this() + { + if(_memory.ptr && --*cast(size_t*)_memory.ptr == 0) + { + debug(std_regex_allocation) writefln("RefCount (dtor): %x %d", + _memory.ptr, *cast(size_t*)_memory.ptr); + free(cast(void*)_memory.ptr); + } + } + + ///Shorthands for front.pre, front.post, front.hit. + @property R pre() + { + return _captures.pre; + } + + ///ditto + @property R post() + { + return _captures.post; + } + + ///ditto + @property R hit() + { + return _captures.hit; + } + + /++ + Functionality for processing subsequent matches of global regexes via range interface: + --- + import std.regex; + auto m = matchAll("Hello, world!", regex(`\w+`)); + assert(m.front.hit == "Hello"); + m.popFront(); + assert(m.front.hit == "world"); + m.popFront(); + assert(m.empty); + --- + +/ + @property auto front() + { + return _captures; + } + + ///ditto + void popFront() + { + + if(counter != 1) + {//do cow magic first + counter--;//we abandon this reference + immutable size = EngineType.initialMemory(_engine.re)+size_t.sizeof; + _memory = (enforce(malloc(size))[0..size]); + _engine = _engine.dupTo(_memory[size_t.sizeof..size]); + counter = 1;//points to new chunk + } + //previous _captures can have escaped references from Capture object + _captures.newMatches(); + _captures._empty = !_engine.match(_captures.matches); + } + + ///ditto + auto save(){ return this; } + + ///Test if this match object is empty. + @property bool empty(){ return _captures._empty; } + + ///Same as !(x.empty), provided for its convenience in conditional statements. + T opCast(T:bool)(){ return !empty; } + + /// Same as .front, provided for compatibility with original std.regex. + @property auto captures(){ return _captures; } + +} + +private @trusted auto matchOnce(alias Engine, RegEx, R)(R input, RegEx re) +{ + import core.stdc.stdlib; + alias Char = BasicElementOf!R; + alias EngineType = Engine!Char; + + size_t size = EngineType.initialMemory(re); + void[] memory = enforce(malloc(size))[0..size]; + scope(exit) free(memory.ptr); + auto captures = Captures!(R, EngineType.DataIndex)(input, re.ngroup, re.dict); + auto engine = EngineType(re, Input!Char(input), memory); + static if(is(RegEx == StaticRegex!(BasicElementOf!R))) + engine.nativeFn = re.nativeFn; + captures._empty = !engine.match(captures.matches); + return captures; +} + +private auto matchMany(alias Engine, RegEx, R)(R input, RegEx re) +{ + re.flags |= RegexOption.global; + return RegexMatch!(R, Engine)(input, re); +} + +unittest +{ + //sanity checks for new API + auto re = regex("abc"); + assert(!"abc".matchOnce!(ThompsonMatcher)(re).empty); + assert("abc".matchOnce!(ThompsonMatcher)(re)[0] == "abc"); +} + + +private enum isReplaceFunctor(alias fun, R) = + __traits(compiles, (Captures!R c) { fun(c); }); + +// the lowest level - just stuff replacements into the sink +private @trusted void replaceCapturesInto(alias output, Sink, R, T) + (ref Sink sink, R input, T captures) + if(isOutputRange!(Sink, dchar) && isSomeString!R) +{ + sink.put(captures.pre); + // a hack to get around bogus errors, should be simply output(captures, sink) + // "is a nested function and cannot be accessed from" + static if(isReplaceFunctor!(output, R)) + sink.put(output(captures)); //"mutator" type of function + else + output(captures, sink); //"output" type of function + sink.put(captures.post); +} + +// ditto for a range of captures +private void replaceMatchesInto(alias output, Sink, R, T) + (ref Sink sink, R input, T matches) + if(isOutputRange!(Sink, dchar) && isSomeString!R) +{ + size_t offset = 0; + foreach(cap; matches) + { + sink.put(cap.pre[offset .. $]); + // same hack, see replaceCapturesInto + static if(isReplaceFunctor!(output, R)) + sink.put(output(cap)); //"mutator" type of function + else + output(cap, sink); //"output" type of function + offset = cap.pre.length + cap.hit.length; + } + sink.put(input[offset .. $]); +} + +// a general skeleton of replaceFirst +private R replaceFirstWith(alias output, R, RegEx)(R input, RegEx re) + if(isSomeString!R && isRegexFor!(RegEx, R)) +{ + auto data = matchFirst(input, re); + if(data.empty) + return input; + auto app = appender!(R)(); + replaceCapturesInto!output(app, input, data); + return app.data; +} + +// ditto for replaceAll +// the method parameter allows old API to ride on the back of the new one +private R replaceAllWith(alias output, + alias method=matchAll, R, RegEx)(R input, RegEx re) + if(isSomeString!R && isRegexFor!(RegEx, R)) +{ + auto matches = method(input, re); //inout(C)[] fails + if(matches.empty) + return input; + auto app = appender!(R)(); + replaceMatchesInto!output(app, input, matches); + return app.data; +} + + +/++ + Start matching $(D input) to regex pattern $(D re), + using Thompson NFA matching scheme. + + The use of this function is $(RED discouraged) - use either of + $(LREF matchAll) or $(LREF matchFirst). + + Delegating the kind of operation + to "g" flag is soon to be phased out along with the + ability to choose the exact matching scheme. The choice of + matching scheme to use depends highly on the pattern kind and + can done automatically on case by case basis. + + Returns: a $(D RegexMatch) object holding engine state after first match. ++/ + +public auto match(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.thompson; + return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, re); +} + +///ditto +public auto match(R, String)(R input, String re) + if(isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson; + return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, regex(re)); +} + +public auto match(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking; + return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re); +} + +/++ + Find the first (leftmost) slice of the $(D input) that + matches the pattern $(D re). This function picks the most suitable + regular expression engine depending on the pattern properties. + + $(D re) parameter can be one of three types: + $(UL + $(LI Plain string, in which case it's compiled to bytecode before matching. ) + $(LI Regex!char (wchar/dchar) that contains a pattern in the form of + compiled bytecode. ) + $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of + compiled native machine code. ) + ) + + Returns: + $(LREF Captures) containing the extent of a match together with all submatches + if there was a match, otherwise an empty $(LREF Captures) object. ++/ +public auto matchFirst(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.thompson; + return matchOnce!ThompsonMatcher(input, re); +} + +///ditto +public auto matchFirst(R, String)(R input, String re) + if(isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson; + return matchOnce!ThompsonMatcher(input, regex(re)); +} + +public auto matchFirst(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking; + return matchOnce!(BacktrackingMatcher!true)(input, re); +} + +/++ + Initiate a search for all non-overlapping matches to the pattern $(D re) + in the given $(D input). The result is a lazy range of matches generated + as they are encountered in the input going left to right. + + This function picks the most suitable regular expression engine + depending on the pattern properties. + + $(D re) parameter can be one of three types: + $(UL + $(LI Plain string, in which case it's compiled to bytecode before matching. ) + $(LI Regex!char (wchar/dchar) that contains a pattern in the form of + compiled bytecode. ) + $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of + compiled native machine code. ) + ) + + Returns: + $(LREF RegexMatch) object that represents matcher state + after the first match was found or an empty one if not present. ++/ +public auto matchAll(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.thompson; + return matchMany!ThompsonMatcher(input, re); +} + +///ditto +public auto matchAll(R, String)(R input, String re) + if(isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson; + return matchMany!ThompsonMatcher(input, regex(re)); +} + +public auto matchAll(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking; + return matchMany!(BacktrackingMatcher!true)(input, re); +} + +// another set of tests just to cover the new API +@system unittest +{ + import std.conv : to; + import std.algorithm : map, equal; + + foreach(String; TypeTuple!(string, wstring, const(dchar)[])) + { + auto str1 = "blah-bleh".to!String(); + auto pat1 = "bl[ae]h".to!String(); + auto mf = matchFirst(str1, pat1); + assert(mf.equal(["blah".to!String()])); + auto mAll = matchAll(str1, pat1); + assert(mAll.equal!((a,b) => a.equal(b)) + ([["blah".to!String()], ["bleh".to!String()]])); + + auto str2 = "1/03/12 - 3/03/12".to!String(); + auto pat2 = regex(r"(\d+)/(\d+)/(\d+)".to!String()); + auto mf2 = matchFirst(str2, pat2); + assert(mf2.equal(["1/03/12", "1", "03", "12"].map!(to!String)())); + auto mAll2 = matchAll(str2, pat2); + assert(mAll2.front.equal(mf2)); + mAll2.popFront(); + assert(mAll2.front.equal(["3/03/12", "3", "03", "12"].map!(to!String)())); + mf2.popFrontN(3); + assert(mf2.equal(["12".to!String()])); + + auto ctPat = ctRegex!(`(?P\d+)/(?P\d+)`.to!String()); + auto str = "2 + 34/56 - 6/1".to!String(); + auto cmf = matchFirst(str, ctPat); + assert(cmf.equal(["34/56", "34", "56"].map!(to!String)())); + assert(cmf["Quot"] == "34".to!String()); + assert(cmf["Denom"] == "56".to!String()); + + auto cmAll = matchAll(str, ctPat); + assert(cmAll.front.equal(cmf)); + cmAll.popFront(); + assert(cmAll.front.equal(["6/1", "6", "1"].map!(to!String)())); + } +} + +/++ + Start matching of $(D input) to regex pattern $(D re), + using traditional $(LUCKY backtracking) matching scheme. + + The use of this function is $(RED discouraged) - use either of + $(LREF matchAll) or $(LREF matchFirst). + + Delegating the kind of operation + to "g" flag is soon to be phased out along with the + ability to choose the exact matching scheme. The choice of + matching scheme to use depends highly on the pattern kind and + can done automatically on case by case basis. + + Returns: a $(D RegexMatch) object holding engine + state after first match. + ++/ +public auto bmatch(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking; + return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, re); +} + +///ditto +public auto bmatch(R, String)(R input, String re) + if(isSomeString!R && isSomeString!String) +{ + import std.regex.internal.backtracking; + return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, regex(re)); +} + +public auto bmatch(R, RegEx)(R input, RegEx re) + if(isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking; + return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re); +} + +// produces replacement string from format using captures for substitution +package void replaceFmt(R, Capt, OutR) + (R format, Capt captures, OutR sink, bool ignoreBadSubs = false) + if(isOutputRange!(OutR, ElementEncodingType!R[]) && + isOutputRange!(OutR, ElementEncodingType!(Capt.String)[])) +{ + import std.algorithm, std.conv; + enum State { Normal, Dollar } + auto state = State.Normal; + size_t offset; +L_Replace_Loop: + while(!format.empty) + final switch(state) + { + case State.Normal: + for(offset = 0; offset < format.length; offset++)//no decoding + { + if(format[offset] == '$') + { + state = State.Dollar; + sink.put(format[0 .. offset]); + format = format[offset+1 .. $];//ditto + continue L_Replace_Loop; + } + } + sink.put(format[0 .. offset]); + format = format[offset .. $]; + break; + case State.Dollar: + if(std.ascii.isDigit(format[0])) + { + uint digit = parse!uint(format); + enforce(ignoreBadSubs || digit < captures.length, text("invalid submatch number ", digit)); + if(digit < captures.length) + sink.put(captures[digit]); + } + else if(format[0] == '{') + { + auto x = find!(a => !std.ascii.isAlpha(a))(format[1..$]); + enforce(!x.empty && x[0] == '}', "no matching '}' in replacement format"); + auto name = format[1 .. $ - x.length]; + format = x[1..$]; + enforce(!name.empty, "invalid name in ${...} replacement format"); + sink.put(captures[name]); + } + else if(format[0] == '&') + { + sink.put(captures[0]); + format = format[1 .. $]; + } + else if(format[0] == '`') + { + sink.put(captures.pre); + format = format[1 .. $]; + } + else if(format[0] == '\'') + { + sink.put(captures.post); + format = format[1 .. $]; + } + else if(format[0] == '$') + { + sink.put(format[0 .. 1]); + format = format[1 .. $]; + } + state = State.Normal; + break; + } + enforce(state == State.Normal, "invalid format string in regex replace"); +} + +/++ + Construct a new string from $(D input) by replacing the first match with + a string generated from it according to the $(D format) specifier. + + To replace all matches use $(LREF replaceAll). + + Params: + input = string to search + re = compiled regular expression to use + format = format string to generate replacements from, + see $(S_LINK Replace format string, the format string). + + Returns: + A string of the same type with the first match (if any) replaced. + If no match is found returns the input string itself. + + Example: + --- + assert(replaceFirst("noon", regex("n"), "[$&]") == "[n]oon"); + --- ++/ +public R replaceFirst(R, C, RegEx)(R input, RegEx re, const(C)[] format) + if(isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) +{ + return replaceFirstWith!((m, sink) => replaceFmt(format, m, sink))(input, re); +} + +/++ + This is a general replacement tool that construct a new string by replacing + matches of pattern $(D re) in the $(D input). Unlike the other overload + there is no format string instead captures are passed to + to a user-defined functor $(D fun) that returns a new string + to use as replacement. + + This version replaces the first match in $(D input), + see $(LREF replaceAll) to replace the all of the matches. + + Returns: + A new string of the same type as $(D input) with all matches + replaced by return values of $(D fun). If no matches found + returns the $(D input) itself. + + Example: + --- + string list = "#21 out of 46"; + string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1)) + (list, regex(`[0-9]+`)); + assert(newList == "#22 out of 46"); + --- ++/ +public R replaceFirst(alias fun, R, RegEx)(R input, RegEx re) + if(isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceFirstWith!((m, sink) => sink.put(fun(m)))(input, re); +} + +/++ + A variation on $(LREF replaceFirst) that instead of allocating a new string + on each call outputs the result piece-wise to the $(D sink). In particular + this enables efficient construction of a final output incrementally. + + Like in $(LREF replaceFirst) family of functions there is an overload + for the substitution guided by the $(D format) string + and the one with the user defined callback. + + Example: + --- + import std.array; + string m1 = "first message\n"; + string m2 = "second message\n"; + auto result = appender!string(); + replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1"); + //equivalent of the above with user-defined callback + replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`)); + assert(result.data == "first\nsecond\n"); + --- ++/ +public @trusted void replaceFirstInto(Sink, R, C, RegEx) + (ref Sink sink, R input, RegEx re, const(C)[] format) + if(isOutputRange!(Sink, dchar) && isSomeString!R + && is(C : dchar) && isRegexFor!(RegEx, R)) + { + replaceCapturesInto!((m, sink) => replaceFmt(format, m, sink)) + (sink, input, matchFirst(input, re)); + } + +///ditto +public @trusted void replaceFirstInto(alias fun, Sink, R, RegEx) + (Sink sink, R input, RegEx re) + if(isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) +{ + replaceCapturesInto!fun(sink, input, matchFirst(input, re)); +} + +//examples for replaceFirst +@system unittest +{ + import std.conv; + string list = "#21 out of 46"; + string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1)) + (list, regex(`[0-9]+`)); + assert(newList == "#22 out of 46"); + import std.array; + string m1 = "first message\n"; + string m2 = "second message\n"; + auto result = appender!string(); + replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1"); + //equivalent of the above with user-defined callback + replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`)); + assert(result.data == "first\nsecond\n"); +} + +/++ + Construct a new string from $(D input) by replacing all of the + fragments that match a pattern $(D re) with a string generated + from the match according to the $(D format) specifier. + + To replace only the first match use $(LREF replaceFirst). + + Params: + input = string to search + re = compiled regular expression to use + format = format string to generate replacements from, + see $(S_LINK Replace format string, the format string). + + Returns: + A string of the same type as $(D input) with the all + of the matches (if any) replaced. + If no match is found returns the input string itself. + + Example: + --- + // Comify a number + auto com = regex(r"(?<=\d)(?=(\d\d\d)+\b)","g"); + assert(replaceAll("12000 + 42100 = 54100", com, ",") == "12,000 + 42,100 = 54,100"); + --- ++/ +public @trusted R replaceAll(R, C, RegEx)(R input, RegEx re, const(C)[] format) + if(isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!((m, sink) => replaceFmt(format, m, sink))(input, re); +} + +/++ + This is a general replacement tool that construct a new string by replacing + matches of pattern $(D re) in the $(D input). Unlike the other overload + there is no format string instead captures are passed to + to a user-defined functor $(D fun) that returns a new string + to use as replacement. + + This version replaces all of the matches found in $(D input), + see $(LREF replaceFirst) to replace the first match only. + + Returns: + A new string of the same type as $(D input) with all matches + replaced by return values of $(D fun). If no matches found + returns the $(D input) itself. + + Params: + input = string to search + re = compiled regular expression + fun = delegate to use + + Example: + Capitalize the letters 'a' and 'r': + --- + string baz(Captures!(string) m) + { + return std.string.toUpper(m.hit); + } + auto s = replaceAll!(baz)("Strap a rocket engine on a chicken.", + regex("[ar]")); + assert(s == "StRAp A Rocket engine on A chicken."); + --- ++/ +public @trusted R replaceAll(alias fun, R, RegEx)(R input, RegEx re) + if(isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!((m, sink) => sink.put(fun(m)))(input, re); +} + +/++ + A variation on $(LREF replaceAll) that instead of allocating a new string + on each call outputs the result piece-wise to the $(D sink). In particular + this enables efficient construction of a final output incrementally. + + As with $(LREF replaceAll) there are 2 overloads - one with a format string, + the other one with a user defined functor. + + Example: + --- + //swap all 3 letter words and bring it back + string text = "How are you doing?"; + auto sink = appender!(char[])(); + replaceAllInto!(cap => retro(cap[0]))(sink, text, regex(`\b\w{3}\b`)); + auto swapped = sink.data.dup; // make a copy explicitly + assert(swapped == "woH era uoy doing?"); + sink.clear(); + replaceAllInto!(cap => retro(cap[0]))(sink, swapped, regex(`\b\w{3}\b`)); + assert(sink.data == text); + --- ++/ +public @trusted void replaceAllInto(Sink, R, C, RegEx) + (Sink sink, R input, RegEx re, const(C)[] format) + if(isOutputRange!(Sink, dchar) && isSomeString!R + && is(C : dchar) && isRegexFor!(RegEx, R)) + { + replaceMatchesInto!((m, sink) => replaceFmt(format, m, sink)) + (sink, input, matchAll(input, re)); + } + +///ditto +public @trusted void replaceAllInto(alias fun, Sink, R, RegEx) + (Sink sink, R input, RegEx re) + if(isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) +{ + replaceMatchesInto!fun(sink, input, matchAll(input, re)); +} + +// a bit of examples +@system unittest +{ + //swap all 3 letter words and bring it back + string text = "How are you doing?"; + auto sink = appender!(char[])(); + replaceAllInto!(cap => retro(cap[0]))(sink, text, regex(`\b\w{3}\b`)); + auto swapped = sink.data.dup; // make a copy explicitly + assert(swapped == "woH era uoy doing?"); + sink.clear(); + replaceAllInto!(cap => retro(cap[0]))(sink, swapped, regex(`\b\w{3}\b`)); + assert(sink.data == text); +} + +// exercise all of the replace APIs +@system unittest +{ + import std.conv; + // try and check first/all simple substitution + foreach(S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[])) + { + S s1 = "curt trial".to!S(); + S s2 = "round dome".to!S(); + S t1F = "court trial".to!S(); + S t2F = "hound dome".to!S(); + S t1A = "court trial".to!S(); + S t2A = "hound home".to!S(); + auto re1 = regex("curt".to!S()); + auto re2 = regex("[dr]o".to!S()); + + assert(replaceFirst(s1, re1, "court") == t1F); + assert(replaceFirst(s2, re2, "ho") == t2F); + assert(replaceAll(s1, re1, "court") == t1A); + assert(replaceAll(s2, re2, "ho") == t2A); + + auto rep1 = replaceFirst!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1); + assert(rep1 == t1F); + assert(replaceFirst!(cap => "ho".to!S())(s2, re2) == t2F); + auto rep1A = replaceAll!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1); + assert(rep1A == t1A); + assert(replaceAll!(cap => "ho".to!S())(s2, re2) == t2A); + + auto sink = appender!S(); + replaceFirstInto(sink, s1, re1, "court"); + assert(sink.data == t1F); + replaceFirstInto(sink, s2, re2, "ho"); + assert(sink.data == t1F~t2F); + replaceAllInto(sink, s1, re1, "court"); + assert(sink.data == t1F~t2F~t1A); + replaceAllInto(sink, s2, re2, "ho"); + assert(sink.data == t1F~t2F~t1A~t2A); + } +} + +/++ + Old API for replacement, operation depends on flags of pattern $(D re). + With "g" flag it performs the equivalent of $(LREF replaceAll) otherwise it + works the same as $(LREF replaceFirst). + + The use of this function is $(RED discouraged), please use $(LREF replaceAll) + or $(LREF replaceFirst) explicitly. ++/ +public R replace(alias scheme = match, R, C, RegEx)(R input, RegEx re, const(C)[] format) + if(isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!((m, sink) => replaceFmt(format, m, sink), match)(input, re); +} + +///ditto +public R replace(alias fun, R, RegEx)(R input, RegEx re) + if(isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!(fun, match)(input, re); +} + +/++ +Range that splits a string using a regular expression as a +separator. + +Example: +---- +auto s1 = ", abc, de, fg, hi, "; +assert(equal(splitter(s1, regex(", *")), + ["", "abc", "de", "fg", "hi", ""])); +---- ++/ +public struct Splitter(Range, alias RegEx = Regex) + if(isSomeString!Range && isRegexFor!(RegEx, Range)) +{ +private: + Range _input; + size_t _offset; + alias Rx = typeof(match(Range.init,RegEx.init)); + Rx _match; + + @trusted this(Range input, RegEx separator) + {//@@@BUG@@@ generated opAssign of RegexMatch is not @trusted + _input = input; + separator.flags |= RegexOption.global; + if (_input.empty) + { + //there is nothing to match at all, make _offset > 0 + _offset = 1; + } + else + { + _match = Rx(_input, separator); + } + } + +public: + auto ref opSlice() + { + return this.save; + } + + ///Forward range primitives. + @property Range front() + { + import std.algorithm : min; + + assert(!empty && _offset <= _match.pre.length + && _match.pre.length <= _input.length); + return _input[_offset .. min($, _match.pre.length)]; + } + + ///ditto + @property bool empty() + { + return _offset > _input.length; + } + + ///ditto + void popFront() + { + assert(!empty); + if (_match.empty) + { + //No more separators, work is done here + _offset = _input.length + 1; + } + else + { + //skip past the separator + _offset = _match.pre.length + _match.hit.length; + _match.popFront(); + } + } + + ///ditto + @property auto save() + { + return this; + } +} + +/** + A helper function, creates a $(D Splitter) on range $(D r) separated by regex $(D pat). + Captured subexpressions have no effect on the resulting range. +*/ +public Splitter!(Range, RegEx) splitter(Range, RegEx)(Range r, RegEx pat) + if(is(BasicElementOf!Range : dchar) && isRegexFor!(RegEx, Range)) +{ + return Splitter!(Range, RegEx)(r, pat); +} + +///An eager version of $(D splitter) that creates an array with splitted slices of $(D input). +public @trusted String[] split(String, RegEx)(String input, RegEx rx) + if(isSomeString!String && isRegexFor!(RegEx, String)) +{ + auto a = appender!(String[])(); + foreach(e; splitter(input, rx)) + a.put(e); + return a.data; +} + +///Exception object thrown in case of errors during regex compilation. +public alias RegexException = std.regex.internal.ir.RegexException; diff --git a/std/signals.d b/std/signals.d index e5d6cb17de4..c683f733e9e 100644 --- a/std/signals.d +++ b/std/signals.d @@ -50,7 +50,7 @@ * SIGNALS=signals * * Copyright: Copyright Digital Mars 2000 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * Source: $(PHOBOSSRC std/_signals.d) */ @@ -62,7 +62,7 @@ module std.signals; import std.stdio; -import std.c.stdlib : calloc, realloc, free; +import core.stdc.stdlib : calloc, realloc, free; import core.exception : onOutOfMemoryError; // Special function for internal use only. @@ -141,7 +141,7 @@ void main() mixin template Signal(T1...) { - static import std.c.stdlib; + static import core.stdc.stdlib; static import core.exception; /*** * A slot is implemented as a delegate. @@ -179,7 +179,7 @@ mixin template Signal(T1...) if (slots.length == 0) { len = 4; - auto p = std.c.stdlib.calloc(slot_t.sizeof, len); + auto p = core.stdc.stdlib.calloc(slot_t.sizeof, len); if (!p) core.exception.onOutOfMemoryError(); slots = (cast(slot_t*)p)[0 .. len]; @@ -187,7 +187,7 @@ mixin template Signal(T1...) else { len = len * 2 + 4; - auto p = std.c.stdlib.realloc(slots.ptr, slot_t.sizeof * len); + auto p = core.stdc.stdlib.realloc(slots.ptr, slot_t.sizeof * len); if (!p) core.exception.onOutOfMemoryError(); slots = (cast(slot_t*)p)[0 .. len]; @@ -252,7 +252,7 @@ mixin template Signal(T1...) * know that this object is destroyed so they are not left * with dangling references to it. */ - if (slots) + if (slots.length) { foreach (slot; slots[0 .. slots_idx]) { @@ -261,7 +261,7 @@ mixin template Signal(T1...) rt_detachDisposeEvent(o, &unhook); } } - std.c.stdlib.free(slots.ptr); + core.stdc.stdlib.free(slots.ptr); slots = null; } } @@ -386,20 +386,20 @@ unittest { a.s2.connect(&o2.watchInt); a.s3.connect(&o3.watchLong); - assert(!o1.i && !o1.l && !o1.str); - assert(!o2.i && !o2.l && !o2.str); - assert(!o3.i && !o3.l && !o3.str); + assert(!o1.i && !o1.l && o1.str == null); + assert(!o2.i && !o2.l && o2.str == null); + assert(!o3.i && !o3.l && o3.str == null); a.value1 = 11; assert(o1.i == 11 && !o1.l && o1.str == "str1"); - assert(!o2.i && !o2.l && !o2.str); - assert(!o3.i && !o3.l && !o3.str); + assert(!o2.i && !o2.l && o2.str == null); + assert(!o3.i && !o3.l && o3.str == null); o1.i = -11; o1.str = "x1"; a.value2 = 12; assert(o1.i == -11 && !o1.l && o1.str == "x1"); assert(o2.i == 12 && !o2.l && o2.str == "str2"); - assert(!o3.i && !o3.l && !o3.str); + assert(!o3.i && !o3.l && o3.str == null); o2.i = -12; o2.str = "x2"; a.value3 = 13; @@ -468,20 +468,20 @@ unittest { a.s5.connect(&o5.watchInt); a.s6.connect(&o6.watchLong); - assert(!o4.i && !o4.l && !o4.str); - assert(!o5.i && !o5.l && !o5.str); - assert(!o6.i && !o6.l && !o6.str); + assert(!o4.i && !o4.l && o4.str == null); + assert(!o5.i && !o5.l && o5.str == null); + assert(!o6.i && !o6.l && o6.str == null); a.value4 = 44; assert(o4.i == 44 && !o4.l && o4.str == "str4"); - assert(!o5.i && !o5.l && !o5.str); - assert(!o6.i && !o6.l && !o6.str); + assert(!o5.i && !o5.l && o5.str == null); + assert(!o6.i && !o6.l && o6.str == null); o4.i = -44; o4.str = "x4"; a.value5 = 45; assert(o4.i == -44 && !o4.l && o4.str == "x4"); assert(o5.i == 45 && !o5.l && o5.str == "str5"); - assert(!o6.i && !o6.l && !o6.str); + assert(!o6.i && !o6.l && o6.str == null); o5.i = -45; o5.str = "x5"; a.value6 = 46; diff --git a/std/socket.d b/std/socket.d index 1d1e2b06c67..f4db5357906 100644 --- a/std/socket.d +++ b/std/socket.d @@ -34,8 +34,9 @@ */ /** + * Socket primitives. * Example: See $(SAMPLESRC listener.d) and $(SAMPLESRC htmlget.d) - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Christopher E. Miller, $(WEB klickverbot.at, David Nadlinger), * $(WEB thecybershadow.net, Vladimir Panteleev) * Source: $(PHOBOSSRC std/_socket.d) @@ -45,7 +46,7 @@ module std.socket; -import core.stdc.stdint, core.stdc.string, std.string, std.c.stdlib, std.conv; +import core.stdc.stdint, core.stdc.string, std.string, core.stdc.stdlib, std.conv; import core.stdc.config; import core.time : dur, Duration; @@ -62,9 +63,9 @@ version(Windows) pragma (lib, "ws2_32.lib"); pragma (lib, "wsock32.lib"); - private import std.c.windows.windows, std.c.windows.winsock, std.windows.syserror; - private alias _ctimeval = std.c.windows.winsock.timeval; - private alias _clinger = std.c.windows.winsock.linger; + private import core.sys.windows.windows, core.sys.windows.winsock2, std.windows.syserror; + private alias _ctimeval = core.sys.windows.winsock2.timeval; + private alias _clinger = core.sys.windows.winsock2.linger; enum socket_t : SOCKET { INVALID_SOCKET } private const int _SOCKET_ERROR = SOCKET_ERROR; @@ -78,33 +79,13 @@ version(Windows) else version(Posix) { version(linux) - import std.c.linux.socket : AF_IPX, AF_APPLETALK, SOCK_RDM, - IPPROTO_IGMP, IPPROTO_GGP, IPPROTO_PUP, IPPROTO_IDP, - SD_RECEIVE, SD_SEND, SD_BOTH, MSG_NOSIGNAL, INADDR_NONE, - TCP_KEEPIDLE, TCP_KEEPINTVL; - else version(OSX) - import std.c.osx.socket : AF_IPX, AF_APPLETALK, SOCK_RDM, - IPPROTO_IGMP, IPPROTO_GGP, IPPROTO_PUP, IPPROTO_IDP, - SD_RECEIVE, SD_SEND, SD_BOTH, INADDR_NONE; - else version(FreeBSD) - { - import core.sys.posix.sys.socket; - import core.sys.posix.sys.select; - import std.c.freebsd.socket; - private enum SD_RECEIVE = SHUT_RD; - private enum SD_SEND = SHUT_WR; - private enum SD_BOTH = SHUT_RDWR; - } - else version(Android) - { - import core.sys.posix.sys.socket; - import core.sys.posix.sys.select; - private enum SD_RECEIVE = SHUT_RD; - private enum SD_SEND = SHUT_WR; - private enum SD_BOTH = SHUT_RDWR; + { + enum : int + { + TCP_KEEPIDLE = 4, + TCP_KEEPINTVL = 5 + } } - else - static assert(false); import core.sys.posix.netdb; import core.sys.posix.sys.un : sockaddr_un; @@ -114,7 +95,7 @@ else version(Posix) private import core.sys.posix.netinet.tcp; private import core.sys.posix.netinet.in_; private import core.sys.posix.sys.time; - //private import core.sys.posix.sys.select; + private import core.sys.posix.sys.select; private import core.sys.posix.sys.socket; private alias _ctimeval = core.sys.posix.sys.time.timeval; private alias _clinger = core.sys.posix.sys.socket.linger; @@ -124,6 +105,12 @@ else version(Posix) enum socket_t : int32_t { init = -1 } private const int _SOCKET_ERROR = -1; + private enum : int + { + SD_RECEIVE = SHUT_RD, + SD_SEND = SHUT_WR, + SD_BOTH = SHUT_RDWR + } private int _lasterr() nothrow @nogc { @@ -202,6 +189,14 @@ string formatSocketError(int err) @trusted else return "Socket error " ~ to!string(err); } + else version (Solaris) + { + auto errs = strerror_r(err, buf.ptr, buf.length); + if (errs == 0) + cs = buf.ptr; + else + return "Socket error " ~ to!string(err); + } else version (Android) { auto errs = strerror_r(err, buf.ptr, buf.length); @@ -1021,7 +1016,7 @@ AddressInfo[] getAddressInfo(T...)(in char[] node, T options) @trusted private AddressInfo[] getAddressInfoImpl(in char[] node, in char[] service, addrinfo* hints) @system { - import std.array : appender; + import std.array : appender; if (getaddrinfoPointer && freeaddrinfoPointer) { @@ -1668,6 +1663,23 @@ public: } } + /** + * Compares with another InternetAddress of same type for equality + * Returns: true if the InternetAddresses share the same address and + * port number. + * Examples: + * -------------- + * InternetAddress addr1,addr2; + * if (addr1 == addr2) { } + * -------------- + */ + override bool opEquals(Object o) const + { + auto other = cast(InternetAddress)o; + return other && this.sin.sin_addr.s_addr == other.sin.sin_addr.s_addr && + this.sin.sin_port == other.sin.sin_port; + } + /** * Parse an IPv4 address string in the dotted-decimal form $(I a.b.c.d) * and return the number. @@ -2175,7 +2187,7 @@ private: final size_t capacity() @property const pure nothrow @nogc { - return set.length / FD_NFDBITS; + return set.length * FD_NFDBITS; } int maxfd; @@ -2225,7 +2237,7 @@ public: auto length = set.length; if (index >= length) { - while (length < index) + while (index >= length) length *= 2; set.length = length; set.length = set.capacity; @@ -2422,6 +2434,17 @@ unittest }); } +unittest // Issue 14012, 14013 +{ + auto set = new SocketSet(1); + assert(set.max >= 0); + + enum LIMIT = 4096; + foreach (n; 0..LIMIT) + set.add(cast(socket_t)n); + assert(set.max >= LIMIT); +} + /// The level at which a socket option is defined: version(Android) { diff --git a/std/stdint.d b/std/stdint.d index 69873b5781b..2d09658d352 100644 --- a/std/stdint.d +++ b/std/stdint.d @@ -116,7 +116,7 @@ * WIKI=Phobos/StdStdint * * Copyright: Copyright Digital Mars 2000 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * Source: $(PHOBOSSRC std/_stdint.d) */ diff --git a/std/stdio.d b/std/stdio.d index 75acf9a561c..db10e5fe879 100644 --- a/std/stdio.d +++ b/std/stdio.d @@ -1,7 +1,7 @@ // Written in the D programming language. /** -Standard I/O functions that extend $(B std.c.stdio). $(B std.c.stdio) +Standard I/O functions that extend $(B core.stdc.stdio). $(B core.stdc.stdio) is $(D_PARAM public)ally imported when importing $(B std.stdio). Source: $(PHOBOSSRC std/_stdio.d) @@ -16,15 +16,19 @@ Authors: $(WEB digitalmars.com, Walter Bright), */ module std.stdio; -public import core.stdc.stdio, std.string : KeepTerminator; -import core.vararg; -static import std.c.stdio; +public import core.stdc.stdio; +import std.typecons;// Flag import std.stdiobase; -import core.stdc.errno, core.stdc.stddef, core.stdc.stdlib, core.memory, - core.stdc.string, core.stdc.wchar_, core.exception; -import std.range; -import std.traits : Unqual, isSomeChar, isAggregateType, isSomeString, - isIntegral, isBoolean, ParameterTypeTuple; +import core.stdc.stddef;// wchar_t +import std.range.primitives;// empty, front, isBidirectionalRange +import std.traits;// Unqual, isSomeChar, isSomeString + + +/++ +If flag $(D KeepTerminator) is set to $(D KeepTerminator.yes), then the delimiter +is included in the strings returned. ++/ +alias KeepTerminator = Flag!"keepTerminator"; version (CRuntime_Microsoft) { @@ -34,14 +38,6 @@ else version (CRuntime_DigitalMars) { // Specific to the way Digital Mars C does stdio version = DIGITAL_MARS_STDIO; - import std.c.stdio : __fhnd_info, FHND_WCHAR, FHND_TEXT; -} - -version (Posix) -{ - import core.sys.posix.fcntl; - import core.sys.posix.stdio; - alias fileno = core.sys.posix.stdio.fileno; } version (linux) @@ -60,6 +56,11 @@ version (FreeBSD) version = GENERIC_IO; } +version (Solaris) +{ + version = GENERIC_IO; +} + version (Android) { version = GENERIC_IO; @@ -189,10 +190,15 @@ else version (GENERIC_IO) int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); } int fputwc_unlocked(wchar_t c, _iobuf* fp) { + import core.stdc.wchar_ : fputwc; return fputwc(c, cast(shared) fp); } int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); } - int fgetwc_unlocked(_iobuf* fp) { return fgetwc(cast(shared) fp); } + int fgetwc_unlocked(_iobuf* fp) + { + import core.stdc.wchar_ : fgetwc; + return fgetwc(cast(shared) fp); + } alias FPUTC = fputc_unlocked; alias FPUTWC = fputwc_unlocked; @@ -320,6 +326,7 @@ Hello, Jimmy! struct File { import std.traits : isScalarType, isArray; + import std.range.primitives : ElementEncodingType; private struct Impl { @@ -332,6 +339,7 @@ struct File package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted { + import core.stdc.stdlib : malloc; import std.exception : enforce; assert(!_p); @@ -447,7 +455,8 @@ Throws: $(D ErrnoException) in case of error. // mucking with the file descriptor. POSIX standard requires the // new fdopen'd file to retain the given file descriptor's // position. - auto fp = core.stdc.stdio.fopen("NUL", stdioOpenmode.tempCString()); + import core.stdc.stdio : fopen; + auto fp = fopen("NUL", stdioOpenmode.tempCString()); errnoEnforce(fp, "Cannot open placeholder NUL stream"); FLOCK(fp); auto iob = cast(_iobuf*)fp; @@ -460,8 +469,11 @@ Throws: $(D ErrnoException) in case of error. { version (Windows) // MSVCRT auto fp = _fdopen(fd, stdioOpenmode.tempCString()); - else - auto fp = .fdopen(fd, stdioOpenmode.tempCString()); + else version (Posix) + { + import core.sys.posix.stdio : fdopen; + auto fp = fdopen(fd, stdioOpenmode.tempCString()); + } errnoEnforce(fp); } this = File(fp, name); @@ -485,7 +497,7 @@ Throws: $(D ErrnoException) in case of error. void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode) { import std.exception : errnoEnforce; - import std.string : format; + import std.format : format; // Create file descriptors from the handles version (DIGITAL_MARS_STDIO) @@ -610,6 +622,7 @@ Throws: $(D ErrnoException) on error. */ void close() @trusted { + import core.stdc.stdlib : free; import std.exception : errnoEnforce; if (!_p) return; // succeed vacuously @@ -625,18 +638,19 @@ Throws: $(D ErrnoException) on error. scope(exit) _p.handle = null; // nullify the handle anyway version (Posix) { - import std.string : format; + import std.format : format; + import core.sys.posix.stdio : pclose; if (_p.isPopened) { - auto res = .pclose(_p.handle); + auto res = pclose(_p.handle); errnoEnforce(res != -1, "Could not close pipe `"~_name~"'"); errnoEnforce(res == 0, format("Command returned %d", res)); return; } } - //fprintf(std.c.stdio.stderr, ("Closing file `"~name~"`.\n\0").ptr); + //fprintf(core.stdc.stdio.stderr, ("Closing file `"~name~"`.\n\0").ptr); errnoEnforce(.fclose(_p.handle) == 0, "Could not close file `"~_name~"'"); } @@ -715,7 +729,7 @@ $(D rawRead) always reads in binary mode on Windows. } } immutable result = - .fread(buffer.ptr, T.sizeof, buffer.length, _p.handle); + fread(buffer.ptr, T.sizeof, buffer.length, _p.handle); errnoEnforce(!error); return result ? buffer[0 .. result] : null; } @@ -808,8 +822,9 @@ Throws: $(D Exception) if the file is not opened. errnoEnforce(fseek(_p.handle, to!int(offset), origin) == 0, "Could not seek in file `"~_name~"'"); } - else + else version (Posix) { + import core.sys.posix.stdio : fseeko, off_t; //static assert(off_t.sizeof == 8); errnoEnforce(fseeko(_p.handle, cast(off_t) offset, origin) == 0, "Could not seek in file `"~_name~"'"); @@ -864,8 +879,9 @@ Throws: $(D Exception) if the file is not opened. { immutable result = ftell(cast(FILE*) _p.handle); } - else + else version (Posix) { + import core.sys.posix.stdio : ftello; immutable result = ftello(cast(FILE*) _p.handle); } errnoEnforce(result != -1, @@ -969,6 +985,7 @@ Throws: $(D Exception) if the file is not opened. ulong start, ulong length) { import std.conv : to; + import core.sys.posix.fcntl : fcntl, flock, off_t; import core.sys.posix.unistd : getpid; flock fl = void; @@ -1003,6 +1020,7 @@ $(UL enforce(isOpen, "Attempting to call lock() on an unopened file"); version (Posix) { + import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK; immutable short type = lockType == LockType.readWrite ? F_WRLCK : F_RDLCK; errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1, @@ -1034,6 +1052,8 @@ specified file segment was already locked. enforce(isOpen, "Attempting to call tryLock() on an unopened file"); version (Posix) { + import core.stdc.errno : EACCES, EAGAIN, errno; + import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK; immutable short type = lockType == LockType.readWrite ? F_WRLCK : F_RDLCK; immutable res = lockImpl(F_SETLK, type, start, length); @@ -1069,6 +1089,7 @@ Removes the lock over the specified file segment. enforce(isOpen, "Attempting to call unlock() on an unopened file"); version (Posix) { + import core.sys.posix.fcntl : F_SETLK, F_UNLCK; errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1, "Could not remove lock for file `"~_name~"'"); } @@ -1112,6 +1133,7 @@ Removes the lock over the specified file segment. // the same process. fork() is used to create a second process. static void runForked(void delegate() code) { + import core.stdc.stdlib : exit; import core.sys.posix.unistd; import core.sys.posix.sys.wait; int child, status; @@ -1166,6 +1188,7 @@ Throws: $(D Exception) if the file is not opened. */ void write(S...)(S args) { + import std.traits : isBoolean, isIntegral, isAggregateType; auto w = lockingTextWriter(); foreach (arg; args) { @@ -1178,6 +1201,7 @@ Throws: $(D Exception) if the file is not opened. } else static if (isSomeString!A) { + import std.range.primitives : put; put(w, arg); } else static if (isIntegral!A) @@ -1192,6 +1216,7 @@ Throws: $(D Exception) if the file is not opened. } else static if (isSomeChar!A) { + import std.range.primitives : put; put(w, arg); } else @@ -1340,7 +1365,7 @@ Params: buf = Buffer used to store the resulting line data. buf is resized as necessary. terminator = Line terminator (by default, $(D '\n')). Use -$(XREF ascii, newline) for portability (unless the file was opened in +$(XREF ascii, newline) for portability (unless the file was opened in text mode). Returns: @@ -1407,6 +1432,7 @@ for every line. isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) { import std.algorithm : endsWith, swap; + import std.range.primitives : back; auto last = terminator.back; C[] buf2; @@ -1490,7 +1516,7 @@ for every line. { import std.exception : errnoEnforce; - return File(errnoEnforce(core.stdc.stdio.tmpfile(), + return File(errnoEnforce(.tmpfile(), "Could not create temporary file with tmpfile()"), null); } @@ -1521,7 +1547,8 @@ Returns the $(D FILE*) corresponding to this object. unittest { - assert(stdout.getFP() == std.c.stdio.stdout); + static import core.stdc.stdio; + assert(stdout.getFP() == core.stdc.stdio.stdout); } /** @@ -1691,7 +1718,7 @@ Char = Character type for each line, defaulting to $(D char). keepTerminator = Use $(D KeepTerminator.yes) to include the terminator at the end of each line. terminator = Line separator ($(D '\n') by default). Use -$(XREF ascii, newline) for portability (unless the file was opened in +$(XREF ascii, newline) for portability (unless the file was opened in text mode). Example: @@ -1701,10 +1728,10 @@ import std.algorithm, std.stdio, std.string; void main() { auto file = File("file.txt"); // Open for reading - const wordCount = file.byLine() // Read lines - .map!split // Split into words - .map!(a => a.length) // Count words per line - .reduce!((a, b) => a + b); // Total word count + const wordCount = file.byLine() // Read lines + .map!split // Split into words + .map!(a => a.length) // Count words per line + .sum(); // Total word count writeln(wordCount); } ---- @@ -1819,7 +1846,11 @@ the contents may well have changed). /** Returns an input range set up to read from the file handle one line -at a time. Each line will be newly allocated. +at a time. Each line will be newly allocated. $(D front) will cache +its value to allow repeated calls without unnecessary allocations. + +Note: Due to caching byLineCopy can be more memory-efficient than +$(D File.byLine.map!idup). The element type for the range will be $(D Char[]). Range primitives may throw $(D StdioException) on I/O error. @@ -1829,12 +1860,12 @@ Char = Character type for each line, defaulting to $(D immutable char). keepTerminator = Use $(D KeepTerminator.yes) to include the terminator at the end of each line. terminator = Line separator ($(D '\n') by default). Use -$(XREF ascii, newline) for portability (unless the file was opened in +$(XREF ascii, newline) for portability (unless the file was opened in text mode). Example: ---- -import std.algorithm, std.stdio; +import std.algorithm, std.array, std.stdio; // Print sorted lines of a file. void main() { @@ -1877,6 +1908,7 @@ $(XREF file,readText) { static import std.file; import std.algorithm : equal; + import std.range; //printf("Entering test at line %d\n", __LINE__); scope(failure) printf("Failed test at line %d\n", __LINE__); @@ -1898,6 +1930,7 @@ $(XREF file,readText) { import std.conv : text; import std.algorithm : sort; + import std.range.primitives : walkLength; uint i; std.file.write(deleteme, txt); @@ -1954,6 +1987,7 @@ $(XREF file,readText) unittest { import std.algorithm : equal; + import std.range; version(Win64) { @@ -2084,14 +2118,24 @@ $(XREF file,readText) @property nothrow ubyte[] front() { - version(assert) if (empty) throw new RangeError(); + version(assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } return chunk_; } /// Ditto void popFront() { - version(assert) if (empty) throw new RangeError(); + version(assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } prime(); } } @@ -2226,6 +2270,7 @@ $(D Range) that locks the file and allows fast writing to it. struct LockingTextWriter { private: + import std.range.primitives : ElementType, isInfinite, isInputRange; FILE* fps_; // the shared file handle _iobuf* handle_; // the unshared version of fps int orientation_; @@ -2239,6 +2284,7 @@ $(D Range) that locks the file and allows fast writing to it. this(ref File f) @trusted { + import core.stdc.wchar_ : fwide; import std.exception : enforce; enforce(f._p && f._p.handle); @@ -2305,6 +2351,7 @@ $(D Range) that locks the file and allows fast writing to it. /// ditto void put(C)(C c) @safe if (is(C : const(dchar))) { + import std.traits : ParameterTypeTuple; static auto trustedFPUTC(int ch, _iobuf* h) @trusted { return FPUTC(ch, h); @@ -2571,6 +2618,7 @@ unittest unittest { static import std.file; + import std.range; auto deleteme = testFilename(); scope(exit) std.file.remove(deleteme); @@ -2608,15 +2656,15 @@ enum LockType struct LockingTextReader { private File _f; - private dchar _crt; + private dchar _front; this(File f) { import std.exception : enforce; - enforce(f.isOpen); _f = f; FLOCK(_f._p.handle); + readFront(); } this(this) @@ -2633,47 +2681,91 @@ struct LockingTextReader void opAssign(LockingTextReader r) { import std.algorithm : swap; - swap(this, r); } @property bool empty() { - import std.exception : enforce; + return !_f.isOpen || _f.eof; + } - if (!_f.isOpen || _f.eof) return true; - if (_crt == _crt.init) + @property dchar front() + { + version(assert) { - _crt = FGETC(cast(_iobuf*) _f._p.handle); - if (_crt == -1) - { - .destroy(_f); - return true; - } - else - { - enforce(ungetc(_crt, cast(FILE*) _f._p.handle) == _crt); - } + import core.exception : RangeError; + if (empty) + throw new RangeError(); } - return false; + return _front; } - @property dchar front() + /* Read a utf8 sequence from the file, removing the chars from the stream. + Returns an empty result when at EOF. */ + private char[] takeFront(return ref char[4] buf) { - version(assert) if (empty) throw new RangeError(); - return _crt; + import std.utf : stride, UTFException; + { + immutable int c = FGETC(cast(_iobuf*) _f._p.handle); + if (c == EOF) + return buf[0 .. 0]; + buf[0] = cast(char) c; + } + immutable seqLen = stride(buf[]); + foreach(ref u; buf[1 .. seqLen]) + { + immutable int c = FGETC(cast(_iobuf*) _f._p.handle); + if (c == EOF) // incomplete sequence + throw new UTFException("Invalid UTF-8 sequence"); + u = cast(char) c; + } + return buf[0 .. seqLen]; } - void popFront() + /* Read a utf8 sequence from the file into _front, putting the chars back so + that they can be read again. + Destroys/closes the file when at EOF. */ + private void readFront() { - version(assert) if (empty) throw new RangeError(); - if (FGETC(cast(_iobuf*) _f._p.handle) == -1) + import std.exception : enforce; + import std.utf : decodeFront; + + char[4] buf; + auto chars = takeFront(buf); + + if (chars.empty) { - import std.exception : enforce; + .destroy(_f); + assert(empty); + return; + } + + auto s = chars; + _front = decodeFront(s); - enforce(_f.eof); + // Put everything back. + foreach(immutable i; 0 .. chars.length) + { + immutable c = chars[$ - 1 - i]; + enforce(ungetc(c, cast(FILE*) _f._p.handle) == c); } - _crt = _crt.init; + } + + void popFront() + { + version(assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + + // Pop the current front. + char[4] buf; + takeFront(buf); + + // Read the next front, leaving the chars on the stream. + readFront(); } // void unget(dchar c) @@ -2685,6 +2777,7 @@ struct LockingTextReader unittest { static import std.file; + import std.range.primitives : isInputRange; static assert(isInputRange!LockingTextReader); auto deleteme = testFilename(); @@ -2701,6 +2794,37 @@ unittest //pragma(msg, "--- todo: readf ---"); } +unittest // bugzilla 13686 +{ + static import std.file; + import std.algorithm : equal; + + auto deleteme = testFilename(); + std.file.write(deleteme, "Тест"); + scope(exit) std.file.remove(deleteme); + + string s; + File(deleteme).readf("%s", &s); + assert(s == "Тест"); + + auto ltr = LockingTextReader(File(deleteme)); + assert(equal(ltr, "Тест")); +} + +unittest // bugzilla 12320 +{ + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "ab"); + scope(exit) std.file.remove(deleteme); + auto ltr = LockingTextReader(File(deleteme)); + assert(ltr.front == 'a'); + ltr.popFront(); + assert(ltr.front == 'b'); + ltr.popFront(); + assert(ltr.empty); +} + /** * Indicates whether $(D T) is a file handle of some kind. */ @@ -2774,6 +2898,7 @@ unittest */ void writeln(T...)(T args) { + import std.traits : isAggregateType; static if (T.length == 0) { import std.exception : enforce; @@ -2790,7 +2915,15 @@ void writeln(T...)(T args) // Specialization for strings - a very frequent case auto w = .trustedStdout.lockingTextWriter(); - w.put(args[0]); + + static if (isStaticArray!(typeof(args[0]))) + { + w.put(args[0][]); + } + else + { + w.put(args[0]); + } w.put('\n'); } else @@ -2810,6 +2943,13 @@ unittest // bug 8040 if (false) writeln(null); if (false) writeln(">", null, "<"); + + // Bugzilla 14041 + if (false) + { + char[8] a; + writeln(a); + } } unittest @@ -2907,44 +3047,27 @@ unittest /*********************************** - * If the first argument $(D args[0]) is a $(D FILE*), use - * $(LINK2 std_format.html#format-string, the format specifier) in - * $(D args[1]) to control the formatting of $(D - * args[2..$]), and write the resulting string to $(D args[0]). - * If $(D arg[0]) is not a $(D FILE*), the call is - * equivalent to $(D writef(stdout, args)). - * +Writes formatted data to standard output (without a trailing newline). -IMPORTANT: - -New behavior starting with D 2.006: unlike previous versions, -$(D writef) (and also $(D writefln)) only scans its first -string argument for format specifiers, but not subsequent string -arguments. This decision was made because the old behavior made it -unduly hard to simply print string variables that occasionally -embedded percent signs. - -Also new starting with 2.006 is support for positional -parameters with -$(LINK2 http://opengroup.org/onlinepubs/009695399/functions/printf.html, -POSIX) syntax. +Params: +args = The first argument $(D args[0]) should be the format string, specifying +how to format the rest of the arguments. For a full description of the syntax +of the format string and how it controls the formatting of the rest of the +arguments, please refer to the documentation for $(XREF format, +formattedWrite). -Example: +Note: In older versions of Phobos, it used to be possible to write: -------------------------- -writef("Date: %2$s %1$s", "October", 5); // "Date: 5 October" ------------------------- +------ +writef(stderr, "%s", "message"); +------ -The positional and non-positional styles can be mixed in the same -format string. (POSIX leaves this behavior undefined.) The internal -counter for non-positional parameters tracks the popFront parameter after -the largest positional parameter already used. +to print a message to $(D stderr). This syntax is no longer supported, and has +been superceded by: -New starting with 2.008: raw format specifiers. Using the "%r" -specifier makes $(D writef) simply write the binary -representation of the argument. Use "%-r" to write numbers in little -endian format, "%+r" to write numbers in big endian format, and "%r" -to write numbers in platform-native format. +------ +stderr.writef("%s", "message"); +------ */ @@ -3148,7 +3271,7 @@ unittest } /* - * Convenience function that forwards to $(D core.stdc.stdio.fopen) + * Convenience function that forwards to $(D core.sys.posix.stdio.fopen) * (to $(D _wfopen) on Windows) * with appropriately-constructed C-style strings. */ @@ -3171,30 +3294,32 @@ private FILE* fopen(in char[] name, in char[] mode = "r") @trusted nothrow @nogc * probably isn't available. Do not use the old transitional API * (the native extern(C) fopen64, http://www.unix.org/version2/whatsnew/lfs20mar.html#3.0) */ - return core.sys.posix.stdio.fopen(name.tempCString(), mode.tempCString()); + import core.sys.posix.stdio : fopen; + return fopen(name.tempCString(), mode.tempCString()); } else { - return core.stdc.stdio.fopen(name.tempCString(), mode.tempCString()); + return .fopen(name.tempCString(), mode.tempCString()); } } version (Posix) { /*********************************** - * Convenience function that forwards to $(D std.c.stdio.popen) + * Convenience function that forwards to $(D core.sys.posix.stdio.popen) * with appropriately-constructed C-style strings. */ FILE* popen(in char[] name, in char[] mode = "r") @trusted nothrow @nogc { + import core.sys.posix.stdio : popen; import std.internal.cstring : tempCString; - return core.sys.posix.stdio.popen(name.tempCString(), mode.tempCString()); + return popen(name.tempCString(), mode.tempCString()); } } /* - * Convenience function that forwards to $(D std.c.stdio.fwrite) + * Convenience function that forwards to $(D core.stdc.stdio.fwrite) * and throws an exception upon error */ private void binaryWrite(T)(FILE* f, T obj) @@ -3287,6 +3412,7 @@ struct lines // if (fileName.length && fclose(f)) // StdioException("Could not close file `"~fileName~"'"); // } + import std.traits : ParameterTypeTuple; alias Parms = ParameterTypeTuple!(dg); static if (isSomeString!(Parms[$ - 1])) { @@ -3332,6 +3458,7 @@ struct lines { import std.exception : assumeUnique; import std.conv : to; + import std.traits : ParameterTypeTuple; alias Parms = ParameterTypeTuple!(dg); enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); @@ -3545,6 +3672,7 @@ private struct ChunksImpl int opApply(D)(scope D dg) { + import core.stdc.stdlib : alloca; enum maxStackSize = 1024 * 16; ubyte[] buffer = void; if (size < maxStackSize) @@ -3554,8 +3682,7 @@ private struct ChunksImpl size_t r = void; int result = 1; uint tally = 0; - while ((r = core.stdc.stdio.fread(buffer.ptr, - buffer[0].sizeof, size, f._p.handle)) > 0) + while ((r = fread(buffer.ptr, buffer[0].sizeof, size, f._p.handle)) > 0) { assert(r <= size); if (r != size) @@ -3614,12 +3741,14 @@ unittest */ class StdioException : Exception { + static import core.stdc.errno; /// Operating system error code. uint errno; /** -Initialize with a message and an error code. */ - this(string message, uint e = .errno) +Initialize with a message and an error code. +*/ + this(string message, uint e = core.stdc.errno.errno) { import std.conv : to; @@ -3641,6 +3770,8 @@ Initialize with a message and an error code. */ } else { + import core.stdc.string : strerror; + auto s = core.stdc.string.strerror(errno); } auto sysmsg = to!string(s); @@ -3659,12 +3790,13 @@ Initialize with a message and an error code. */ /// ditto static void opCall() { - throw new StdioException(null, .errno); + throw new StdioException(null, core.stdc.errno.errno); } } extern(C) void std_stdio_static_this() { + static import core.stdc.stdio; //Bind stdin, stdout, stderr __gshared File.Impl stdinImpl; stdinImpl.handle = core.stdc.stdio.stdin; @@ -3682,7 +3814,25 @@ extern(C) void std_stdio_static_this() //--------- __gshared { - File stdin; /// The standard input stream. + /** The standard input stream. + */ + File stdin; + /// + unittest + { + // Read stdin, sort lines, write to stdout + import std.stdio, std.array, std.algorithm : sort, copy; + + void main() { + stdin // read from stdin + .byLineCopy(KeepTerminator.yes) // copying each line + .array() // convert to array of lines + .sort() // sort the lines + .copy( // copy output of .sort to an OutputRange + stdout.lockingTextWriter()); // the OutputRange + } + } + File stdout; /// The standard output stream. File stderr; /// The standard error stream. } @@ -3715,6 +3865,10 @@ unittest version (DIGITAL_MARS_STDIO) private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n') { + import core.memory; + import core.stdc.string : memcpy; + import std.array : appender, uninitializedArray; + FLOCK(fps); scope(exit) FUNLOCK(fps); @@ -3856,6 +4010,9 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n') version (MICROSOFT_STDIO) private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n') { + import core.memory; + import std.array : appender, uninitializedArray; + FLOCK(fps); scope(exit) FUNLOCK(fps); @@ -3892,6 +4049,9 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n') version (GCC_IO) private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n') { + import core.memory; + import core.stdc.stdlib : free; + import core.stdc.wchar_ : fwide; import std.utf : encode; if (fwide(fps, 0) > 0) @@ -3980,6 +4140,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n') version (GENERIC_IO) private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n') { + import core.stdc.wchar_ : fwide; import std.utf : encode; FLOCK(fps); @@ -4093,14 +4254,17 @@ version(linux) { File openNetwork(string host, ushort port) { - static import linux = std.c.linux.linux; - static import sock = std.c.linux.socket; + static import sock = core.sys.posix.sys.socket; + static import core.sys.posix.unistd; import core.stdc.string : memcpy; + import core.sys.posix.arpa.inet : htons; + import core.sys.posix.netdb : gethostbyname; + import core.sys.posix.netinet.in_ : sockaddr_in; import std.conv : to; import std.exception : enforce; import std.internal.cstring : tempCString; - auto h = enforce( sock.gethostbyname(host.tempCString()), + auto h = enforce( gethostbyname(host.tempCString()), new StdioException("gethostbyname")); int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); @@ -4108,18 +4272,17 @@ version(linux) scope(failure) { - linux.close(s); // want to make sure it doesn't dangle if - // something throws. Upon normal exit, the - // File struct's reference counting takes - // care of closing, so we don't need to - // worry about success + // want to make sure it doesn't dangle if something throws. Upon + // normal exit, the File struct's reference counting takes care of + // closing, so we don't need to worry about success + core.sys.posix.unistd.close(s); } - sock.sockaddr_in addr; + sockaddr_in addr; addr.sin_family = sock.AF_INET; - addr.sin_port = sock.htons(port); - core.stdc.string.memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); + addr.sin_port = htons(port); + memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, new StdioException("Connect failed")); diff --git a/std/stdiobase.d b/std/stdiobase.d index 805d8b6ceba..f12e409a531 100644 --- a/std/stdiobase.d +++ b/std/stdiobase.d @@ -5,7 +5,7 @@ * std.stdio, to eliminate cyclic construction errors. * * Copyright: Copyright Andrei Alexandrescu 2008 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB erdani.org, Andrei Alexandrescu) * Source: $(PHOBOSSRC std/_stdiobase.d) */ diff --git a/std/stream.d b/std/stream.d index 480574e4197..c52c27d564a 100644 --- a/std/stream.d +++ b/std/stream.d @@ -389,7 +389,7 @@ interface OutputStream { // not really abstract, but its instances will do nothing useful class Stream : InputStream, OutputStream { - private import std.string, std.digest.crc, std.c.stdlib, std.c.stdio; + 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. @@ -1204,25 +1204,13 @@ class Stream : InputStream, OutputStream { // writes data to stream using printf() syntax, // returns number of bytes written - version (Win64) - size_t printf(const(char)[] format, ...) { - return vprintf(format, _argptr); - } - else version (X86_64) size_t printf(const(char)[] format, ...) { va_list ap; - va_start(ap, __va_argsave); + va_start(ap, format); auto result = vprintf(format, ap); va_end(ap); return result; } - else - size_t printf(const(char)[] format, ...) { - va_list ap; - ap = cast(va_list) &format; - ap += format.sizeof; - return vprintf(format, ap); - } private void doFormatCallback(dchar c) { char[4] buf; @@ -1913,7 +1901,7 @@ enum FileMode { } version (Windows) { - private import std.c.windows.windows; + private import core.sys.windows.windows; extern (Windows) { void FlushFileBuffers(HANDLE hFile); DWORD GetFileType(HANDLE hFile); diff --git a/std/string.d b/std/string.d index dbeed9149e2..c1d4e410da7 100644 --- a/std/string.d +++ b/std/string.d @@ -1,8 +1,12 @@ // Written in the D programming language. /** - +String handling functions. Note that many typical string functions are found in +$(D std.algorithm) because all D strings are bidirectional ranges. +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) $(TR $(TDNW Searching) @@ -50,8 +54,8 @@ $(TR $(TDNW Miscellaneous) ) ) ) +) -This module presents String handling functions. Objects of types $(D _string), $(D wstring), and $(D dstring) are value types and cannot be mutated element-by-element. For using mutation during building strings, use $(D char[]), $(D wchar[]), or $(D dchar[]). The $(D xxxstring) @@ -63,8 +67,16 @@ to Unicode and ASCII are found in $(LINK2 std_uni.html, std.uni) and $(LINK2 std_ascii.html, std.ascii), respectively. Other functions that have a wider generality than just strings can be found in std.algorithm and std.range. +Functions +$(XREF uni, icmp) +$(XREF uni, toLower) +$(XREF uni, toLowerInPlace) +$(XREF uni, toUpper) +$(XREF uni, toUpperInPlace) +$(XREF format, format) +are publicly imported. + Macros: WIKI = Phobos/StdString -MYREF = $1  Copyright: Copyright Digital Mars 2007-. @@ -79,23 +91,26 @@ Source: $(PHOBOSSRC std/_string.d) */ module std.string; -//debug=string; // uncomment to turn on debugging printf's -debug(string) import core.stdc.stdio; +//debug=string; // uncomment to turn on debugging trustedPrintf's -import core.exception : RangeError, onRangeError; -import core.vararg, core.stdc.stdlib, core.stdc.string, - std.algorithm, std.ascii, std.conv, std.exception, std.format, std.functional, - std.range, std.traits, - std.typecons, std.typetuple, std.uni, std.utf; +debug(string) private +void trustedPrintf(in char* str) @trusted nothrow @nogc +{ + import core.stdc.stdio : printf; + printf("%s", str); +} -//Remove when repeat is finally removed. They're only here as part of the -//deprecation of these functions in std.string. -public import std.algorithm : startsWith, endsWith, cmp, count; -public import std.array : join, split; +public import std.uni : icmp, toLower, toLowerInPlace, toUpper, toUpperInPlace; +public import std.format : format, sformat; +import std.typecons : Flag; -version(Windows) import core.stdc.wchar_ : wcslen, wcscmp; +import std.range.primitives; +import std.traits; +import std.typetuple; -version(unittest) import std.algorithm : filter; +//public imports for backward compatibility +public import std.algorithm : startsWith, endsWith, cmp, count; +public import std.array : join, split; /* ************* Exceptions *************** */ @@ -121,73 +136,6 @@ class StringException : Exception } -/++ - Compares two ranges of characters lexicographically. The comparison is - case insensitive. Use $(XREF algorithm, cmp) for a case sensitive - comparison. For details see $(XREF uni, _icmp). - - $(BOOKTABLE, - $(TR $(TD $(D < 0)) $(TD $(D s1 < s2) )) - $(TR $(TD $(D = 0)) $(TD $(D s1 == s2))) - $(TR $(TD $(D > 0)) $(TD $(D s1 > s2))) - ) -+/ -alias icmp = std.uni.icmp; - -unittest -{ - debug(string) printf("string.icmp.unittest\n"); - - assertCTFEable!( - { - assert(icmp("Ü", "ü") == 0, "Über failure"); - assert(icmp("abc", "abc") == 0); - assert(icmp("ABC", "abc") == 0); - assert(icmp("abc"w, "abc") == 0); - assert(icmp("ABC", "abc"w) == 0); - assert(icmp("abc"d, "abc") == 0); - assert(icmp("ABC", "abc"d) == 0); - assert(icmp(cast(char[])"abc", "abc") == 0); - assert(icmp("ABC", cast(char[])"abc") == 0); - assert(icmp(cast(wchar[])"abc"w, "abc") == 0); - assert(icmp("ABC", cast(wchar[])"abc"w) == 0); - assert(icmp(cast(dchar[])"abc"d, "abc") == 0); - assert(icmp("ABC", cast(dchar[])"abc"d) == 0); - assert(icmp(cast(string)null, cast(string)null) == 0); - assert(icmp("", "") == 0); - assert(icmp("abc", "abcd") < 0); - assert(icmp("abcd", "abc") > 0); - assert(icmp("abc", "abd") < 0); - assert(icmp("bbc", "abc") > 0); - assert(icmp("abc", "abc"w) == 0); - assert(icmp("ABC"w, "abc") == 0); - assert(icmp("", ""w) == 0); - assert(icmp("abc"w, "abcd") < 0); - assert(icmp("abcd", "abc"w) > 0); - assert(icmp("abc", "abd") < 0); - assert(icmp("bbc"w, "abc") > 0); - assert(icmp("aaa", "aaaa"d) < 0); - assert(icmp("aaaa"w, "aaa"d) > 0); - assert(icmp("aaa"d, "aaa"w) == 0); - assert(icmp("\u0430\u0411\u0543"d, "\u0430\u0411\u0543") == 0); - assert(icmp("\u0430\u0411\u0543"d, "\u0431\u0410\u0544") < 0); - assert(icmp("\u0431\u0411\u0544"d, "\u0431\u0410\u0543") > 0); - assert(icmp("\u0430\u0410\u0543"d, "\u0430\u0410\u0544") < 0); - assert(icmp("\u0430\u0411\u0543"d, "\u0430\u0411\u0543\u0237") < 0); - assert(icmp("\u0430\u0411\u0543\u0237"d, "\u0430\u0411\u0543") > 0); - - assert(icmp("aaa", filter!"true"("aaa")) == 0); - assert(icmp(filter!"true"("aaa"), "aaa") == 0); - assert(icmp(filter!"true"("aaa"), filter!"true"("aaa")) == 0); - assert(icmp(filter!"true"("\u0430\u0411\u0543"d), "\u0430\u0411\u0543") == 0); - assert(icmp(filter!"true"("\u0430\u0411\u0543"d), "\u0431\u0410\u0544"w) < 0); - assert(icmp("\u0431\u0411\u0544"d, filter!"true"("\u0431\u0410\u0543"w)) > 0); - assert(icmp("\u0430\u0410\u0543"d, filter!"true"("\u0430\u0410\u0544")) < 0); - assert(icmp(filter!"true"("\u0430\u0411\u0543"d), filter!"true"("\u0430\u0411\u0543\u0237")) < 0); - assert(icmp(filter!"true"("\u0430\u0411\u0543\u0237"d), filter!"true"("\u0430\u0411\u0543")) > 0); - }); -} - /++ Returns a D-style array of $(D char) given a zero-terminated C-style string. The returned array will retain the same type qualifiers as the input. @@ -197,6 +145,7 @@ unittest +/ inout(char)[] fromStringz(inout(char)* cString) @system pure { + import core.stdc.string : strlen; return cString ? cString[0 .. strlen(cString)] : null; } @@ -227,6 +176,7 @@ in } out (result) { + import core.stdc.string : strlen, memcmp; if (result) { auto slen = s.length; @@ -237,6 +187,7 @@ out (result) } body { + import std.exception : assumeUnique; /+ Unfortunately, this isn't reliable. We could make this work if string literals are put in read-only memory and we test if s[] is pointing into @@ -282,7 +233,10 @@ immutable(char)* toStringz(in string s) @trusted pure nothrow pure nothrow unittest { - debug(string) printf("string.toStringz.unittest\n"); + import core.stdc.string : strlen; + import std.conv : to; + + debug(string) trustedPrintf("string.toStringz.unittest\n"); // TODO: CTFEable toStringz is really necessary? //assertCTFEable!( @@ -315,7 +269,7 @@ pure nothrow unittest /** Flag indicating whether a search is case-sensitive. */ -enum CaseSensitive { no, yes } +alias CaseSensitive = Flag!"caseSensitive"; /++ Returns the index of the first occurrence of $(D c) in $(D s). If $(D c) @@ -327,10 +281,13 @@ ptrdiff_t indexOf(Char)(in Char[] s, in dchar c, in CaseSensitive cs = CaseSensitive.yes) @safe pure if (isSomeChar!Char) { + import std.ascii : toLower, isASCII; + import std.uni : toLower; if (cs == CaseSensitive.yes) { static if (Char.sizeof == 1) { + import core.stdc.string : memchr; if (std.ascii.isASCII(c) && !__ctfe) { // Plain old ASCII auto trustedmemchr() @trusted { return cast(Char*)memchr(s.ptr, c, s.length); } @@ -379,8 +336,10 @@ ptrdiff_t indexOf(Char)(in Char[] s, in dchar c, @safe pure unittest { - debug(string) printf("string.indexOf.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.indexOf.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) @@ -438,7 +397,8 @@ ptrdiff_t indexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx, @safe pure unittest { - debug(string) printf("string.indexOf(startIdx).unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.indexOf(startIdx).unittest\n"); foreach (S; TypeTuple!(string, wstring, dstring)) { @@ -486,6 +446,8 @@ ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, in CaseSensitive cs = CaseSensitive.yes) @trusted if (isSomeChar!Char1 && isSomeChar!Char2) { + import std.uni : toLower; + import std.algorithm : find; const(Char1)[] balance; if (cs == CaseSensitive.yes) { @@ -502,14 +464,16 @@ ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, @safe pure unittest { - debug(string) printf("string.indexOf.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.indexOf.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(indexOf(cast(S)null, to!T("a")) == -1); assert(indexOf(to!S("def"), to!T("a")) == -1); assert(indexOf(to!S("abba"), to!T("a")) == 0); @@ -539,7 +503,7 @@ ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, // Thanks to Carlos Santander B. and zwang assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y", to!T("page-break-before"), CaseSensitive.no) == -1); - } + }(); foreach (cs; EnumMembers!CaseSensitive) { @@ -578,12 +542,13 @@ ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, @safe pure unittest { - debug(string) printf("string.indexOf(startIdx).unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.indexOf(startIdx).unittest\n"); foreach(S; TypeTuple!(string, wstring, dstring)) { foreach(T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(indexOf(cast(S)null, to!T("a"), 1337) == -1); assert(indexOf(to!S("def"), to!T("a"), 0) == -1); assert(indexOf(to!S("abba"), to!T("a"), 2) == 3); @@ -620,7 +585,7 @@ ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, // In order for indexOf with and without index to be consistent assert(indexOf(to!S(""), to!T("")) == indexOf(to!S(""), to!T(""), 0)); - } + }(); foreach(cs; EnumMembers!CaseSensitive) { @@ -644,6 +609,8 @@ ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in CaseSensitive cs = CaseSensitive.yes) @safe pure if (isSomeChar!Char) { + import std.ascii : isASCII, toLower; + import std.utf : canSearchInCodeUnits; if (cs == CaseSensitive.yes) { if (canSearchInCodeUnits!Char(c)) @@ -702,8 +669,10 @@ ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, @safe pure unittest { - debug(string) printf("string.lastIndexOf.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.lastIndexOf.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) @@ -762,7 +731,9 @@ ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx, @safe pure unittest { - debug(string) printf("string.lastIndexOf.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.lastIndexOf.unittest\n"); foreach(S; TypeTuple!(string, wstring, dstring)) { @@ -802,6 +773,9 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, in CaseSensitive cs = CaseSensitive.yes) @safe pure if (isSomeChar!Char1 && isSomeChar!Char2) { + import std.utf : strideBack; + import std.conv : to; + import std.algorithm : endsWith; if (sub.empty) return s.length; @@ -812,6 +786,8 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, { static if (is(Unqual!Char1 == Unqual!Char2)) { + import core.stdc.string : memcmp; + immutable c = sub[0]; for (ptrdiff_t i = s.length - sub.length; i >= 0; --i) @@ -833,7 +809,8 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, { return memcmp(s1, s2, n); } - if (trustedMemcmp(&s[i + 1], &sub[1], sub.length - 1) == 0) + if (trustedMemcmp(&s[i + 1], &sub[1], + (sub.length - 1) * Char1.sizeof) == 0) return i; } } @@ -871,14 +848,17 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, @safe pure unittest { - debug(string) printf("string.lastIndexOf.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.lastIndexOf.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 enum typeStr = S.stringof ~ " " ~ T.stringof; assert(lastIndexOf(cast(S)null, to!T("a")) == -1, typeStr); @@ -913,7 +893,7 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, assert(lastIndexOf(sPlts, to!T("FOuRTh"), CaseSensitive.no) == 10, typeStr); assert(lastIndexOf(sMars, to!T("whO\'s \'MY"), CaseSensitive.no) == 0, typeStr); assert(lastIndexOf(sMars, to!T(sMars), CaseSensitive.no) == 0, typeStr); - } + }(); foreach (cs; EnumMembers!CaseSensitive) { @@ -927,6 +907,23 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, }); } +@safe pure unittest // issue13529 +{ + import std.conv : to; + foreach (S; TypeTuple!(string, wstring, dstring)) + { + foreach (T; TypeTuple!(string, wstring, dstring)) + { + enum typeStr = S.stringof ~ " " ~ T.stringof; + auto idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö ö")); + assert(idx != -1, to!string(idx) ~ " " ~ typeStr); + + idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö öd")); + assert(idx == -1, to!string(idx) ~ " " ~ typeStr); + } + } +} + /++ Returns the index of the last occurrence of $(D sub) in $(D s). If $(D sub) is not found, then $(D -1) is returned. The $(D startIdx) slices $(D s) in @@ -951,12 +948,14 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, @safe pure unittest { - debug(string) printf("string.lastIndexOf.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.lastIndexOf.unittest\n"); foreach(S; TypeTuple!(string, wstring, dstring)) { foreach(T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 enum typeStr = S.stringof ~ " " ~ T.stringof; assert(lastIndexOf(cast(S)null, to!T("a")) == -1, typeStr); @@ -984,7 +983,7 @@ ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 4, CaseSensitive.no) == 2, typeStr); assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), 6, CaseSensitive.no) == 3, typeStr); assert(lastIndexOf(to!S(""), to!T(""), 0) == lastIndexOf(to!S(""), to!T("")), typeStr); - } + }(); foreach(cs; EnumMembers!CaseSensitive) { @@ -1002,12 +1001,14 @@ private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)( in CaseSensitive cs = CaseSensitive.yes) @safe pure if (isSomeChar!Char && isSomeChar!Char2) { + import std.algorithm : canFind; if (cs == CaseSensitive.yes) { static if (forward) { static if (any) { + import std.algorithm : findAmong; size_t n = haystack.findAmong(needles).length; return n ? haystack.length - n : -1; } @@ -1026,6 +1027,9 @@ private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)( { static if (any) { + import std.utf : strideBack; + import std.algorithm : findAmong; + import std.range : retro; size_t n = haystack.retro.findAmong(needles).source.length; if (n) { @@ -1128,6 +1132,8 @@ ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, /// @safe pure unittest { + import std.conv : to; + ptrdiff_t i = "helloWorld".indexOfAny("Wr"); assert(i == 5); i = "öällo world".indexOfAny("lo "); @@ -1136,14 +1142,17 @@ ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, @safe pure unittest { - debug(string) printf("string.indexOfAny.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.indexOfAny.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(indexOfAny(cast(S)null, to!T("a")) == -1); assert(indexOfAny(to!S("def"), to!T("rsa")) == -1); assert(indexOfAny(to!S("abba"), to!T("a")) == 0); @@ -1166,7 +1175,7 @@ ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, CaseSensitive.no) == 0); assert(indexOfAny("\u0100", to!T("\u0100"), CaseSensitive.no) == 0); - } + }(); } } ); @@ -1208,6 +1217,8 @@ ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, /// @safe pure unittest { + import std.conv : to; + ptrdiff_t i = "helloWorld".indexOfAny("Wr", 4); assert(i == 5); @@ -1217,12 +1228,14 @@ ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, @safe pure unittest { - debug(string) printf("string.indexOfAny(startIdx).unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.indexOfAny(startIdx).unittest\n"); foreach(S; TypeTuple!(string, wstring, dstring)) { foreach(T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(indexOfAny(cast(S)null, to!T("a"), 1337) == -1); assert(indexOfAny(to!S("def"), to!T("AaF"), 0) == -1); assert(indexOfAny(to!S("abba"), to!T("NSa"), 2) == 3); @@ -1247,7 +1260,7 @@ ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, assert(indexOfAny("\u0100", to!T("\u0100"), 0, CaseSensitive.no) == 0); - } + }(); foreach(cs; EnumMembers!CaseSensitive) { @@ -1290,14 +1303,17 @@ ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, @safe pure unittest { - debug(string) printf("string.lastIndexOfAny.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.lastIndexOfAny.unittest\n"); + + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(lastIndexOfAny(cast(S)null, to!T("a")) == -1); assert(lastIndexOfAny(to!S("def"), to!T("rsa")) == -1); assert(lastIndexOfAny(to!S("abba"), to!T("a")) == 3); @@ -1334,7 +1350,7 @@ ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, assert(lastIndexOfAny("\u0100", to!T("\u0100"), CaseSensitive.no) == 0); - } + }(); } } ); @@ -1372,6 +1388,8 @@ ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, /// @safe pure unittest { + import std.conv : to; + ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo", 4); assert(i == 3); @@ -1381,14 +1399,17 @@ ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, @safe pure unittest { - debug(string) printf("string.lastIndexOfAny(index).unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.lastIndexOfAny(index).unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 enum typeStr = S.stringof ~ " " ~ T.stringof; assert(lastIndexOfAny(cast(S)null, to!T("a"), 1337) == -1, @@ -1424,7 +1445,7 @@ ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, CaseSensitive.no) == -1, typeStr); assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2, CaseSensitive.no) == 0, typeStr); - } + }(); } } ); @@ -1458,14 +1479,17 @@ ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, @safe pure unittest { - debug(string) printf("string.indexOf.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.indexOf.unittest\n"); + + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(indexOfNeither(cast(S)null, to!T("a")) == -1); assert(indexOfNeither("abba", "a") == 1); @@ -1493,7 +1517,7 @@ ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), CaseSensitive.no))); } - } + }(); } } ); @@ -1540,14 +1564,17 @@ ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, @safe pure unittest { - debug(string) printf("string.indexOfNeither(index).unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.indexOfNeither(index).unittest\n"); + + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(indexOfNeither(cast(S)null, to!T("a"), 1) == -1); assert(indexOfNeither(to!S("def"), to!T("a"), 1) == 1, to!string(indexOfNeither(to!S("def"), to!T("a"), 1))); @@ -1574,7 +1601,7 @@ ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, CaseSensitive.no) == 2, to!string(indexOfNeither( to!S("öDfEfffg"), to!T("äDi"), 2, CaseSensitive.no))); } - } + }(); } } ); @@ -1607,14 +1634,17 @@ ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, @safe pure unittest { - debug(string) printf("string.lastIndexOfNeither.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.lastIndexOfNeither.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(lastIndexOfNeither(cast(S)null, to!T("a")) == -1); assert(lastIndexOfNeither(to!S("def"), to!T("rsa")) == 2); assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2); @@ -1643,7 +1673,7 @@ ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), CaseSensitive.no) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), CaseSensitive.no))); - } + }(); } } ); @@ -1685,14 +1715,17 @@ ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, @safe pure unittest { - debug(string) printf("string.lastIndexOfNeither(index).unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.lastIndexOfNeither(index).unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring)) { foreach (T; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(lastIndexOfNeither(cast(S)null, to!T("a"), 1337) == -1); assert(lastIndexOfNeither(to!S("def"), to!T("f")) == 1); assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2); @@ -1720,7 +1753,7 @@ ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2, CaseSensitive.no) == 1, to!string(lastIndexOfNeither( to!S("dfefffg"), to!T("NSA"), 2, CaseSensitive.no))); - } + }(); } } ); @@ -1750,6 +1783,9 @@ auto representation(Char)(Char[] s) @safe pure nothrow @nogc @trusted pure unittest { + import std.exception; + import std.typecons; + assertCTFEable!( { void test(Char, T)(Char[] str) @@ -1776,35 +1812,6 @@ auto representation(Char)(Char[] s) @safe pure nothrow @nogc } -/++ - Returns a string which is identical to $(D s) except that all of its - characters are converted to lowercase (by preforming Unicode lowercase mapping). - If none of $(D s) characters were affected, then $(D s) itself is returned. - +/ -alias toLower = std.uni.toLower; -/++ - Converts $(D s) to lowercase (by performing Unicode lowercase mapping) in place. - For a few characters string length may increase after the transformation, - in such a case the function reallocates exactly once. - If $(D s) does not have any uppercase characters, then $(D s) is unaltered. - +/ -alias toLowerInPlace = std.uni.toLowerInPlace; - -/++ - Returns a string which is identical to $(D s) except that all of its - characters are converted to uppercase (by preforming Unicode uppercase mapping). - If none of $(D s) characters were affected, then $(D s) itself is returned. - +/ -alias toUpper = std.uni.toUpper; - -/++ - Converts $(D s) to uppercase (by performing Unicode uppercase mapping) in place. - For a few characters string length may increase after the transformation, - in such a case the function reallocates exactly once. - If $(D s) does not have any lowercase characters, then $(D s) is unaltered. - +/ -alias toUpperInPlace = std.uni.toUpperInPlace; - /++ Capitalize the first character of $(D s) and convert the rest of $(D s) to lowercase. @@ -1812,6 +1819,8 @@ alias toUpperInPlace = std.uni.toUpperInPlace; S capitalize(S)(S s) @trusted pure if (isSomeString!S) { + import std.utf : encode; + Unqual!(typeof(s[0]))[] retval; bool changed = false; @@ -1847,6 +1856,10 @@ S capitalize(S)(S s) @trusted pure @trusted pure unittest { + import std.conv : to; + import std.algorithm : cmp; + + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(string, wstring, dstring, char[], wchar[], dchar[])) @@ -1885,11 +1898,15 @@ S capitalize(S)(S s) @trusted pure If $(D keepTerm) is set to $(D KeepTerminator.yes), then the delimiter is included in the strings returned. +/ -enum KeepTerminator : bool { no, yes } +alias KeepTerminator = Flag!"keepTerminator"; /// ditto S[] splitLines(S)(S s, in KeepTerminator keepTerm = KeepTerminator.no) @safe pure if (isSomeString!S) { + import std.utf : decode; + import std.uni : lineSep, paraSep; + import std.array : appender; + size_t iStart = 0; size_t nextI = 0; auto retval = appender!(S[])(); @@ -1927,8 +1944,11 @@ S[] splitLines(S)(S s, in KeepTerminator keepTerm = KeepTerminator.no) @safe pur @safe pure unittest { - debug(string) printf("string.splitLines.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.splitLines.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) @@ -1995,6 +2015,7 @@ C[] stripLeft(C)(C[] str) @safe pure @nogc /// @safe pure unittest { + import std.uni : lineSep, paraSep; assert(stripLeft(" hello world ") == "hello world "); assert(stripLeft("\n\t\v\rhello world\n\t\v\r") == @@ -2019,6 +2040,7 @@ C[] stripLeft(C)(C[] str) @safe pure @nogc C[] stripRight(C)(C[] str) @safe pure @nogc if (isSomeChar!C) { + import std.utf : codeLength; foreach_reverse (i, dchar c; str) { if (!std.uni.isWhite(c)) @@ -2031,6 +2053,7 @@ C[] stripRight(C)(C[] str) @safe pure @nogc /// @safe pure unittest { + import std.uni : lineSep, paraSep; assert(stripRight(" hello world ") == " hello world"); assert(stripRight("\n\t\v\rhello world\n\t\v\r") == @@ -2059,6 +2082,7 @@ C[] strip(C)(C[] str) @safe pure /// @safe pure unittest { + import std.uni : lineSep, paraSep; assert(strip(" hello world ") == "hello world"); assert(strip("\n\t\v\rhello world\n\t\v\r") == @@ -2073,8 +2097,12 @@ C[] strip(C)(C[] str) @safe pure @safe pure unittest { - debug(string) printf("string.strip.unittest\n"); + import std.conv : to; + import std.algorithm : equal; + debug(string) trustedPrintf("string.strip.unittest\n"); + + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!( char[], const char[], string, @@ -2106,6 +2134,8 @@ C[] strip(C)(C[] str) @safe pure @safe pure unittest { + import std.exception; + import std.range; assertCTFEable!( { wstring s = " "; @@ -2128,6 +2158,7 @@ C[] strip(C)(C[] str) @safe pure C[] chomp(C)(C[] str) @safe pure nothrow @nogc if (isSomeChar!C) { + import std.uni : lineSep, paraSep; if (str.empty) return str; @@ -2175,6 +2206,7 @@ C1[] chomp(C1, C2)(C1[] str, const(C2)[] delimiter) @safe pure static if (is(Unqual!C1 == Unqual!C2)) { + import std.algorithm : endsWith; if (str.endsWith(delimiter)) return str[0 .. $ - delimiter.length]; return str; @@ -2198,6 +2230,8 @@ C1[] chomp(C1, C2)(C1[] str, const(C2)[] delimiter) @safe pure /// @safe pure unittest { + import std.utf : decode; + import std.uni : lineSep, paraSep; assert(chomp(" hello world \n\r") == " hello world \n"); assert(chomp(" hello world \r\n") == " hello world "); assert(chomp(" hello world \n\n") == " hello world \n"); @@ -2217,9 +2251,12 @@ C1[] chomp(C1, C2)(C1[] str, const(C2)[] delimiter) @safe pure unittest { - debug(string) printf("string.chomp.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.chomp.unittest\n"); string s; + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) @@ -2240,7 +2277,7 @@ unittest assert(chomp(to!S("hello\u2029\u2029")) == "hello\u2029"); foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 // @@@ BUG IN COMPILER, MUST INSERT CAST assert(chomp(cast(S)null, cast(T)null) is null); assert(chomp(to!S("hello\n"), cast(T)null) == "hello"); @@ -2251,7 +2288,7 @@ unittest assert(chomp(to!S("hello"), to!T("llo")) == "he"); assert(chomp(to!S("\uFF28ello"), to!T("llo")) == "\uFF28e"); assert(chomp(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")) == "\uFF28e"); - } + }(); } }); } @@ -2267,12 +2304,15 @@ C1[] chompPrefix(C1, C2)(C1[] str, C2[] delimiter) @safe pure { static if (is(Unqual!C1 == Unqual!C2)) { + import std.algorithm : startsWith; if (str.startsWith(delimiter)) return str[delimiter.length .. $]; return str; } else { + import std.utf : decode; + auto orig = str; size_t index = 0; @@ -2297,18 +2337,21 @@ C1[] chompPrefix(C1, C2)(C1[] str, C2[] delimiter) @safe pure /* @safe */ pure unittest { + import std.conv : to; + import std.algorithm : equal; + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) { foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(equal(chompPrefix(to!S("abcdefgh"), to!T("abcde")), "fgh")); assert(equal(chompPrefix(to!S("abcde"), to!T("abcdefgh")), "abcde")); assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el\uFF4co")), "")); assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")), "\uFF4co")); assert(equal(chompPrefix(to!S("\uFF28el"), to!T("\uFF28el\uFF4co")), "\uFF28el")); - } + }(); } }); } @@ -2347,8 +2390,12 @@ S chop(S)(S str) @safe pure unittest { - debug(string) printf("string.chop.unittest\n"); + import std.conv : to; + import std.algorithm : equal; + debug(string) trustedPrintf("string.chop.unittest\n"); + + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) @@ -2373,6 +2420,9 @@ unittest S leftJustify(S)(S s, size_t width, dchar fillChar = ' ') @trusted pure if (isSomeString!S) { + import std.utf : canSearchInCodeUnits; + import std.conv : to; + alias C = ElementEncodingType!S; if (canSearchInCodeUnits!C(fillChar)) @@ -2408,6 +2458,9 @@ S leftJustify(S)(S s, size_t width, dchar fillChar = ' ') @trusted pure S rightJustify(S)(S s, size_t width, dchar fillChar = ' ') @trusted pure if (isSomeString!S) { + import std.utf : canSearchInCodeUnits; + import std.conv : to; + alias C = ElementEncodingType!S; if (canSearchInCodeUnits!C(fillChar)) @@ -2443,6 +2496,9 @@ S rightJustify(S)(S s, size_t width, dchar fillChar = ' ') @trusted pure S center(S)(S s, size_t width, dchar fillChar = ' ') @trusted pure if (isSomeString!S) { + import std.utf : canSearchInCodeUnits; + import std.conv : to; + alias C = ElementEncodingType!S; if (canSearchInCodeUnits!C(fillChar)) @@ -2475,8 +2531,11 @@ S center(S)(S s, size_t width, dchar fillChar = ' ') @trusted pure @trusted pure unittest { - debug(string) printf("string.justify.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.justify.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) @@ -2515,6 +2574,9 @@ S center(S)(S s, size_t width, dchar fillChar = ' ') @trusted pure S detab(S)(S s, size_t tabSize = 8) @trusted pure if (isSomeString!S) { + import std.utf : encode; + import std.uni : lineSep, paraSep; + assert(tabSize > 0); alias C = Unqual!(typeof(s[0])); bool changes = false; @@ -2569,8 +2631,12 @@ S detab(S)(S s, size_t tabSize = 8) @trusted pure @trusted pure unittest { - debug(string) printf("string.detab.unittest\n"); + import std.conv : to; + import std.algorithm : cmp; + + debug(string) trustedPrintf("string.detab.unittest\n"); + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring)) @@ -2601,6 +2667,10 @@ S detab(S)(S s, size_t tabSize = 8) @trusted pure S entab(S)(S s, size_t tabSize = 8) @trusted pure if (isSomeString!S) { + import std.utf : encode; + import std.uni : lineSep, paraSep; + import std.exception : assumeUnique; + bool changes = false; alias C = Unqual!(typeof(s[0])); C[] result; @@ -2696,8 +2766,11 @@ S entab(S)(S s, size_t tabSize = 8) @trusted pure @safe pure unittest { - debug(string) printf("string.entab.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.entab.unittest\n"); + import std.exception; assertCTFEable!( { assert(entab(cast(string) null) is null); @@ -2757,6 +2830,7 @@ C1[] translate(C1, C2 = immutable char)(C1[] str, const(C2)[] toRemove = null) @safe pure if (isSomeChar!C1 && isSomeChar!C2) { + import std.array : appender; auto buffer = appender!(C1[])(); translateImpl(str, transTable, toRemove, buffer); return buffer.data; @@ -2787,7 +2861,9 @@ C1[] translate(C1, C2 = immutable char)(C1[] str, @trusted pure unittest { + import std.conv : to; + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!( char[], const( char)[], immutable( char)[], @@ -2807,7 +2883,7 @@ C1[] translate(C1, C2 = immutable char)(C1[] str, foreach (T; TypeTuple!( char[], const( char)[], immutable( char)[], wchar[], const(wchar)[], immutable(wchar)[], dchar[], const(dchar)[], immutable(dchar)[])) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 foreach(R; TypeTuple!(dchar[dchar], const dchar[dchar], immutable dchar[dchar])) { @@ -2819,7 +2895,7 @@ C1[] translate(C1, C2 = immutable char)(C1[] str, assert(translate(to!S("hello world"), tt, to!T("q5")) == to!S("qe55o wor5d")); } - } + }(); auto s = to!S("hello world"); dchar[dchar] transTable = ['h' : 'q', 'l' : '5']; @@ -2834,6 +2910,7 @@ C1[] translate(C1, S, C2 = immutable char)(C1[] str, const(C2)[] toRemove = null) @safe pure if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2) { + import std.array : appender; auto buffer = appender!(C1[])(); translateImpl(str, transTable, toRemove, buffer); return buffer.data; @@ -2841,6 +2918,9 @@ C1[] translate(C1, S, C2 = immutable char)(C1[] str, @trusted pure unittest { + import std.conv : to; + + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!( char[], const( char)[], immutable( char)[], @@ -2864,7 +2944,7 @@ C1[] translate(C1, S, C2 = immutable char)(C1[] str, foreach (T; TypeTuple!( char[], const( char)[], immutable( char)[], wchar[], const(wchar)[], immutable(wchar)[], dchar[], const(dchar)[], immutable(dchar)[])) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 foreach(R; TypeTuple!(string[dchar], const string[dchar], immutable string[dchar])) @@ -2881,7 +2961,7 @@ C1[] translate(C1, S, C2 = immutable char)(C1[] str, assert(translate(to!S("hello world"), tt, to!T("42")) == to!S("yellowe4242o wor42d")); } - } + }(); auto s = to!S("hello world"); string[dchar] transTable = ['h' : "silly", 'l' : "putty"]; @@ -2912,6 +2992,7 @@ void translate(C1, C2 = immutable char, Buffer)(C1[] str, /// @safe pure unittest { + import std.array : appender; dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; auto buffer = appender!(dchar[])(); translate("hello world", transTable1, null, buffer); @@ -2929,6 +3010,7 @@ void translate(C1, C2 = immutable char, Buffer)(C1[] str, @safe pure unittest // issue 13018 { + import std.array : appender; immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; auto buffer = appender!(dchar[])(); translate("hello world", transTable1, null, buffer); @@ -3041,6 +3123,7 @@ body string makeTrans(in char[] from, in char[] to) @trusted pure nothrow in { + import std.ascii : isASCII; assert(from.length == to.length); assert(from.length <= 256); foreach (char c; from) @@ -3050,6 +3133,8 @@ in } body { + import std.exception : assumeUnique; + char[] transTable = new char[256]; foreach (i; 0 .. transTable.length) @@ -3071,6 +3156,9 @@ body @safe pure unittest { + import std.conv : to; + + import std.exception; assertCTFEable!( { foreach (C; TypeTuple!(char, const char, immutable char)) @@ -3087,14 +3175,14 @@ body assert(translate(to!S("hello world"), makeTrans("hl", "q5")) == to!S("qe55o wor5d")); assert(translate(to!S("hello \U00010143 world"), makeTrans("hl", "q5")) == to!S("qe55o \U00010143 wor5d")); - assert(translate(to!S("hello world"), makeTrans("ol", "1o")), to!S("heool wlrdd")); + assert(translate(to!S("hello world"), makeTrans("ol", "1o")) == to!S("heoo1 w1rod")); assert(translate(to!S("hello world"), makeTrans("", "")) == to!S("hello world")); assert(translate(to!S("hello world"), makeTrans("12345", "67890")) == to!S("hello world")); assert(translate(to!S("hello \U00010143 world"), makeTrans("12345", "67890")) == to!S("hello \U00010143 world")); foreach (T; TypeTuple!(char[], const(char)[], immutable(char)[])) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(translate(to!S("hello world"), makeTrans("hl", "q5"), to!T("r")) == to!S("qe55o wo5d")); assert(translate(to!S("hello \U00010143 world"), makeTrans("hl", "q5"), to!T("r")) == @@ -3103,7 +3191,7 @@ body to!S(" wrd")); assert(translate(to!S("hello world"), makeTrans("hl", "q5"), to!T("q5")) == to!S("qe55o wor5d")); - } + }(); } }); } @@ -3138,6 +3226,7 @@ body /// @safe pure unittest { + import std.array : appender; auto buffer = appender!(char[])(); auto transTable1 = makeTrans("eo5", "57q"); translate("hello world", transTable1, null, buffer); @@ -3171,209 +3260,6 @@ private void translateImplAscii(C = immutable char, Buffer)(in char[] str, } } -/***************************************************** - * Format arguments into a string. - * - * Params: fmt = Format string. For detailed specification, see $(XREF format,formattedWrite). - * args = Variadic list of arguments to format into returned string. - * - * $(RED format's current implementation has been replaced with $(LREF xformat)'s - * implementation. in November 2012. - * This is seamless for most code, but it makes it so that the only - * argument that can be a format string is the first one, so any - * code which used multiple format strings has broken. Please change - * your calls to format accordingly. - * - * e.g.: - * ---- - * format("key = %s", key, ", value = %s", value) - * ---- - * needs to be rewritten as: - * ---- - * format("key = %s, value = %s", key, value) - * ---- - * ) - */ -string format(Char, Args...)(in Char[] fmt, Args args) -{ - auto w = appender!string(); - auto n = formattedWrite(w, fmt, args); - version (all) - { - // In the future, this check will be removed to increase consistency - // with formattedWrite - enforce(n == args.length, new FormatException( - text("Orphan format arguments: args[", n, "..", args.length, "]"))); - } - return w.data; -} - -unittest -{ - debug(string) printf("std.string.format.unittest\n"); - - assertCTFEable!( - { -// assert(format(null) == ""); - assert(format("foo") == "foo"); - assert(format("foo%%") == "foo%"); - assert(format("foo%s", 'C') == "fooC"); - assert(format("%s foo", "bar") == "bar foo"); - assert(format("%s foo %s", "bar", "abc") == "bar foo abc"); - assert(format("foo %d", -123) == "foo -123"); - assert(format("foo %d", 123) == "foo 123"); - - assertThrown!FormatException(format("foo %s")); - assertThrown!FormatException(format("foo %s", 123, 456)); - - assert(format("hel%slo%s%s%s", "world", -138, 'c', true) == - "helworldlo-138ctrue"); - }); -} - - -/***************************************************** - * Format arguments into buffer buf which must be large - * enough to hold the result. Throws RangeError if it is not. - * Returns: The slice of $(D buf) containing the formatted string. - * - * $(RED sformat's current implementation has been replaced with $(LREF xsformat)'s - * implementation. in November 2012. - * This is seamless for most code, but it makes it so that the only - * argument that can be a format string is the first one, so any - * code which used multiple format strings has broken. Please change - * your calls to sformat accordingly. - * - * e.g.: - * ---- - * sformat(buf, "key = %s", key, ", value = %s", value) - * ---- - * needs to be rewritten as: - * ---- - * sformat(buf, "key = %s, value = %s", key, value) - * ---- - * ) - */ -char[] sformat(Char, Args...)(char[] buf, in Char[] fmt, Args args) -{ - size_t i; - - struct Sink - { - void put(dchar c) - { - char[4] enc; - auto n = encode(enc, c); - - if (buf.length < i + n) - onRangeError("std.string.sformat", 0); - - buf[i .. i + n] = enc[0 .. n]; - i += n; - } - void put(const(char)[] s) - { - if (buf.length < i + s.length) - onRangeError("std.string.sformat", 0); - - buf[i .. i + s.length] = s[]; - i += s.length; - } - void put(const(wchar)[] s) - { - for (; !s.empty; s.popFront()) - put(s.front); - } - void put(const(dchar)[] s) - { - for (; !s.empty; s.popFront()) - put(s.front); - } - } - auto n = formattedWrite(Sink(), fmt, args); - version (all) - { - // In the future, this check will be removed to increase consistency - // with formattedWrite - enforce(n == args.length, new FormatException( - text("Orphan format arguments: args[", n, "..", args.length, "]"))); - } - return buf[0 .. i]; -} - -unittest -{ - debug(string) printf("std.string.sformat.unittest\n"); - - assertCTFEable!( - { - char[10] buf; - - assert(sformat(buf[], "foo") == "foo"); - assert(sformat(buf[], "foo%%") == "foo%"); - assert(sformat(buf[], "foo%s", 'C') == "fooC"); - assert(sformat(buf[], "%s foo", "bar") == "bar foo"); - assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc")); - assert(sformat(buf[], "foo %d", -123) == "foo -123"); - assert(sformat(buf[], "foo %d", 123) == "foo 123"); - - assertThrown!FormatException(sformat(buf[], "foo %s")); - assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456)); - - assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); - }); -} - -// Explicitly undocumented. It will be removed in July 2014. -deprecated("Please use std.string.format instead.") alias xformat = format; - -deprecated unittest -{ - debug(string) printf("std.string.xformat.unittest\n"); - - assertCTFEable!( - { -// assert(xformat(null) == ""); - assert(xformat("foo") == "foo"); - assert(xformat("foo%%") == "foo%"); - assert(xformat("foo%s", 'C') == "fooC"); - assert(xformat("%s foo", "bar") == "bar foo"); - assert(xformat("%s foo %s", "bar", "abc") == "bar foo abc"); - assert(xformat("foo %d", -123) == "foo -123"); - assert(xformat("foo %d", 123) == "foo 123"); - - assertThrown!FormatException(xformat("foo %s")); - assertThrown!FormatException(xformat("foo %s", 123, 456)); - }); -} - -// Explicitly undocumented. It will be removed in July 2014. -deprecated("Please use std.string.sformat instead.") alias xsformat = sformat; - -deprecated unittest -{ - debug(string) printf("std.string.xsformat.unittest\n"); - - assertCTFEable!( - { - char[10] buf; - - assert(xsformat(buf[], "foo") == "foo"); - assert(xsformat(buf[], "foo%%") == "foo%"); - assert(xsformat(buf[], "foo%s", 'C') == "fooC"); - assert(xsformat(buf[], "%s foo", "bar") == "bar foo"); - assertThrown!RangeError(xsformat(buf[], "%s foo %s", "bar", "abc")); - assert(xsformat(buf[], "foo %d", -123) == "foo -123"); - assert(xsformat(buf[], "foo %d", 123) == "foo 123"); - - assertThrown!FormatException(xsformat(buf[], "foo %s")); - assertThrown!FormatException(xsformat(buf[], "foo %s", 123, 456)); - - assert(xsformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); - }); -} - - /*********************************************** * See if character c is in the pattern. * Patterns: @@ -3428,8 +3314,11 @@ bool inPattern(S)(dchar c, in S pattern) @safe pure @nogc if (isSomeString!S) @safe pure @nogc unittest { - debug(string) printf("std.string.inPattern.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("std.string.inPattern.unittest\n"); + import std.exception; assertCTFEable!( { assert(inPattern('x', "x") == 1); @@ -3488,8 +3377,11 @@ size_t countchars(S, S1)(S s, in S1 pattern) @safe pure @nogc if (isSomeString!S @safe pure @nogc unittest { - debug(string) printf("std.string.count.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("std.string.count.unittest\n"); + + import std.exception; assertCTFEable!( { assert(countchars("abc", "a-c") == 3); @@ -3504,6 +3396,8 @@ size_t countchars(S, S1)(S s, in S1 pattern) @safe pure @nogc if (isSomeString!S S removechars(S)(S s, in S pattern) @safe pure if (isSomeString!S) { + import std.utf : encode; + Unqual!(typeof(s[0]))[] r; bool changed = false; @@ -3531,8 +3425,11 @@ S removechars(S)(S s, in S pattern) @safe pure if (isSomeString!S) @safe pure unittest { - debug(string) printf("std.string.removechars.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("std.string.removechars.unittest\n"); + + import std.exception; assertCTFEable!( { assert(removechars("abc", "a-c").length == 0); @@ -3551,6 +3448,8 @@ S removechars(S)(S s, in S pattern) @safe pure if (isSomeString!S) S squeeze(S)(S s, in S pattern = null) { + import std.utf : encode; + Unqual!(typeof(s[0]))[] r; dchar lastc; size_t lasti; @@ -3592,8 +3491,11 @@ S squeeze(S)(S s, in S pattern = null) @trusted pure unittest { - debug(string) printf("std.string.squeeze.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("std.string.squeeze.unittest\n"); + + import std.exception; assertCTFEable!( { string s; @@ -3667,6 +3569,8 @@ S1 munch(S1, S2)(ref S1 s, S2 pattern) @safe pure @nogc S succ(S)(S s) @safe pure if (isSomeString!S) { + import std.ascii : isAlphaNum; + if (s.length && std.ascii.isAlphaNum(s[$ - 1])) { auto r = s.dup; @@ -3720,8 +3624,11 @@ S succ(S)(S s) @safe pure if (isSomeString!S) @safe pure unittest { - debug(string) printf("std.string.succ.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("std.string.succ.unittest\n"); + import std.exception; assertCTFEable!( { assert(succ(string.init) is null); @@ -3776,6 +3683,10 @@ S succ(S)(S s) @safe pure if (isSomeString!S) C1[] tr(C1, C2, C3, C4 = immutable char) (C1[] str, const(C2)[] from, const(C3)[] to, const(C4)[] modifiers = null) { + import std.conv : conv_to = to; + import std.utf : decode; + import std.array : appender; + bool mod_c; bool mod_d; bool mod_s; @@ -3792,7 +3703,7 @@ C1[] tr(C1, C2, C3, C4 = immutable char) } if (to.empty && !mod_d) - to = std.conv.to!(typeof(to))(from); + to = conv_to!(typeof(to))(from); auto result = appender!(C1[])(); bool modified; @@ -3885,8 +3796,10 @@ C1[] tr(C1, C2, C3, C4 = immutable char) unittest { - debug(string) printf("std.string.tr.unittest\n"); - import std.algorithm; + import std.conv : to; + + debug(string) trustedPrintf("std.string.tr.unittest\n"); + import std.algorithm : equal; // Complete list of test types; too slow to test'em all // alias TestTypes = TypeTuple!( @@ -3897,6 +3810,7 @@ unittest // Reduced list of test types alias TestTypes = TypeTuple!(char[], const(wchar)[], immutable(dchar)[]); + import std.exception; assertCTFEable!( { foreach (S; TestTypes) @@ -3975,6 +3889,8 @@ unittest bool isNumeric(const(char)[] s, in bool bAllowSep = false) @safe pure { + import std.algorithm : among; + immutable iLen = s.length; if (iLen == 0) return false; @@ -4100,8 +4016,11 @@ bool isNumeric(const(char)[] s, in bool bAllowSep = false) @safe pure @trusted unittest { - debug(string) printf("isNumeric(in string, bool = false).unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("isNumeric(in string, bool = false).unittest\n"); + import std.exception; assertCTFEable!( { // Test the isNumeric(in string) function @@ -4249,6 +4168,7 @@ body @safe pure nothrow unittest { + import std.exception; assertCTFEable!( { char[4] buffer; @@ -4331,10 +4251,13 @@ body string[string] abbrev(string[] values) @safe pure { + import std.algorithm : sort; + string[string] result; // Make a copy when sorting so we follow COW principles. - values = values.dup.sort; // @@@BUG@@@ not CTFEable + values = values.dup; + sort(values); size_t values_length = values.length; size_t lasti = values_length; @@ -4375,11 +4298,14 @@ string[string] abbrev(string[] values) @safe pure @trusted pure unittest { - debug(string) printf("string.abbrev.unittest\n"); + import std.conv : to; + import std.algorithm : sort; - // @@@BUG@@@ Built-in arr.sort is not CTFEable - //assertCTFEable!( - //{ + debug(string) trustedPrintf("string.abbrev.unittest\n"); + + import std.exception; + assertCTFEable!( + { string[] values; values ~= "hello"; values ~= "hello"; @@ -4389,7 +4315,7 @@ string[string] abbrev(string[] values) @safe pure r = abbrev(values); auto keys = r.keys.dup; - keys.sort; + sort(keys); assert(keys.length == 4); assert(keys[0] == "he"); @@ -4401,17 +4327,24 @@ string[string] abbrev(string[] values) @safe pure assert(r[keys[1]] == "hello"); assert(r[keys[2]] == "hello"); assert(r[keys[3]] == "hello"); - //}); + }); } /****************************************** - * Compute column number after string if string starts in the - * leftmost column, which is numbered starting from 0. + * Compute _column number at the end of the printed form of the string, + * assuming the string starts in the leftmost _column, which is numbered + * starting from 0. + * + * Tab characters are expanded into enough spaces to bring the _column number + * to the next multiple of tabsize, and carriage returns and newlines reset the + * running _column number back to 0. */ size_t column(S)(S str, in size_t tabsize = 8) @safe pure @nogc if (isSomeString!S) { + import std.uni : lineSep, paraSep; + size_t column; foreach (dchar c; str) @@ -4437,10 +4370,37 @@ size_t column(S)(S str, in size_t tabsize = 8) @safe pure @nogc if (isSomeString return column; } +/// +unittest +{ + assert(column("1234 ") == 5); + + // Tab stops are set at 8 spaces by default; tab characters insert enough + // spaces to bring the column position to the next multiple of 8. + assert(column("\t") == 8); + assert(column("1\t") == 8); + assert(column("\t1") == 9); + assert(column("123\t") == 8); + + // Other tab widths are possible by specifying it explicitly: + assert(column("\t", 4) == 4); + assert(column("1\t", 4) == 4); + assert(column("\t1", 4) == 5); + assert(column("123\t", 4) == 4); + + // Newlines and carriage returns reset the column number. + assert(column("abc\n") == 0); + assert(column("abc\n1") == 1); + assert(column("abcdefg\r1234") == 4); +} + @safe @nogc unittest { - debug(string) printf("string.column.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.column.unittest\n"); + import std.exception; assertCTFEable!( { assert(column(string.init) == 0); @@ -4536,8 +4496,11 @@ S wrap(S)(S s, in size_t columns = 80, S firstindent = null, @safe pure unittest { - debug(string) printf("string.wrap.unittest\n"); + import std.conv : to; + debug(string) trustedPrintf("string.wrap.unittest\n"); + + import std.exception; assertCTFEable!( { assert(wrap(string.init) == "\n"); @@ -4569,6 +4532,8 @@ S outdent(S)(S str) @safe pure if(isSomeString!S) /// ditto S[] outdent(S)(S[] lines) @safe pure if(isSomeString!S) { + import std.algorithm : startsWith; + if (lines.empty) { return null; @@ -4646,7 +4611,9 @@ void main() { @safe pure unittest { - debug(string) printf("string.outdent.unittest\n"); + import std.conv : to; + + debug(string) trustedPrintf("string.outdent.unittest\n"); template outdent_testStr(S) { @@ -4672,6 +4639,7 @@ void main() { "; } + import std.exception; assertCTFEable!( { @@ -4747,6 +4715,7 @@ See_Also: $(LREF representation) auto assumeUTF(T)(T[] arr) pure if(staticIndexOf!(Unqual!T, ubyte, ushort, uint) != -1) { + import std.utf : validate; alias ToUTFType(U) = TypeTuple!(char, wchar, dchar)[U.sizeof / 2]; auto asUTF = cast(ModifyTypePreservingSTC!(ToUTFType, T)[])arr; debug validate(asUTF); @@ -4765,6 +4734,7 @@ auto assumeUTF(T)(T[] arr) pure pure unittest { + import std.algorithm : equal; foreach(T; TypeTuple!(char[], wchar[], dchar[])) { immutable T jti = "Hello World"; diff --git a/std/syserror.d b/std/syserror.d index 8b5f8be064c..d55429c3fc7 100644 --- a/std/syserror.d +++ b/std/syserror.d @@ -15,7 +15,7 @@ module std.syserror; deprecated("Please use std.windows.syserror.sysErrorString instead") class SysError { - private import std.c.stdio; + private import core.stdc.stdio; private import core.stdc.string; private import std.string; diff --git a/std/traits.d b/std/traits.d index 3f2647e0c60..42426d4cd61 100644 --- a/std/traits.d +++ b/std/traits.d @@ -1,61 +1,62 @@ // Written in the D programming language. /** - * Templates with which to extract information about types and symbols at - * compile time. + * Templates which extract information about types and symbols at compile time. * - * + * $(SCRIPT inhibitQuickIndex = 1;) * + * $(DIVC quickindex, * $(BOOKTABLE , * $(TR $(TH Category) $(TH Templates)) * $(TR $(TD Symbol Name _traits) $(TD - * $(LREF packageName) - * $(LREF moduleName) * $(LREF fullyQualifiedName) + * $(LREF moduleName) + * $(LREF packageName) * )) * $(TR $(TD Function _traits) $(TD - * $(LREF ReturnType) - * $(LREF ParameterTypeTuple) * $(LREF arity) - * $(LREF ParameterStorageClassTuple) - * $(LREF ParameterIdentifierTuple) - * $(LREF ParameterDefaultValueTuple) * $(LREF functionAttributes) - * $(LREF isSafe) - * $(LREF isUnsafe) * $(LREF functionLinkage) - * $(LREF variadicFunctionStyle) * $(LREF FunctionTypeOf) + * $(LREF isSafe) + * $(LREF isUnsafe) + * $(LREF ParameterDefaultValueTuple) + * $(LREF ParameterIdentifierTuple) + * $(LREF ParameterStorageClassTuple) + * $(LREF ParameterTypeTuple) + * $(LREF ReturnType) * $(LREF SetFunctionAttributes) + * $(LREF variadicFunctionStyle) * )) * $(TR $(TD Aggregate Type _traits) $(TD - * $(LREF isNested) - * $(LREF hasNested) + * $(LREF BaseClassesTuple) + * $(LREF BaseTypeTuple) + * $(LREF classInstanceAlignment) + * $(LREF EnumMembers) + * $(LREF FieldNameTuple) * $(LREF FieldTypeTuple) - * $(LREF RepresentationTypeTuple) * $(LREF hasAliasing) - * $(LREF hasIndirections) - * $(LREF hasUnsharedAliasing) - * $(LREF hasElaborateCopyConstructor) * $(LREF hasElaborateAssign) + * $(LREF hasElaborateCopyConstructor) * $(LREF hasElaborateDestructor) + * $(LREF hasIndirections) * $(LREF hasMember) - * $(LREF EnumMembers) - * $(LREF BaseTypeTuple) - * $(LREF BaseClassesTuple) + * $(LREF hasNested) + * $(LREF hasUnsharedAliasing) * $(LREF InterfacesTuple) - * $(LREF TransitiveBaseTypeTuple) + * $(LREF isNested) * $(LREF MemberFunctionsTuple) - * $(LREF TemplateOf) + * $(LREF RepresentationTypeTuple) * $(LREF TemplateArgsOf) - * $(LREF classInstanceAlignment) + * $(LREF TemplateOf) + * $(LREF TransitiveBaseTypeTuple) * )) * $(TR $(TD Type Conversion) $(TD * $(LREF CommonType) * $(LREF ImplicitConversionTargets) - * $(LREF isImplicitlyConvertible) * $(LREF isAssignable) * $(LREF isCovariantWith) + * $(LREF isImplicitlyConvertible) * )) * - * $(TR $(TD IsSomething) $(TD + * $(TR $(TD Categories of types) $(TD + * $(LREF isAggregateType) + * $(LREF isArray) + * $(LREF isAssociativeArray) + * $(LREF isBasicType) * $(LREF isBoolean) - * $(LREF isIntegral) + * $(LREF isBuiltinType) + * $(LREF isDynamicArray) * $(LREF isFloatingPoint) + * $(LREF isIntegral) + * $(LREF isNarrowString) * $(LREF isNumeric) + * $(LREF isPointer) * $(LREF isScalarType) - * $(LREF isBasicType) - * $(LREF isUnsigned) * $(LREF isSigned) * $(LREF isSomeChar) * $(LREF isSomeString) - * $(LREF isNarrowString) * $(LREF isStaticArray) - * $(LREF isDynamicArray) - * $(LREF isArray) - * $(LREF isAssociativeArray) - * $(LREF isBuiltinType) - * $(LREF isPointer) - * $(LREF isAggregateType) + * $(LREF isUnsigned) * )) - * $(TR $(TD xxx) $(TD - * $(LREF isIterable) - * $(LREF isMutable) - * $(LREF isInstanceOf) + * $(TR $(TD Type behaviours) $(TD + * $(LREF isAbstractClass) + * $(LREF isAbstractFunction) + * $(LREF isCallable) + * $(LREF isDelegate) * $(LREF isExpressionTuple) - * $(LREF isTypeTuple) + * $(LREF isFinalClass) + * $(LREF isFinalFunction) * $(LREF isFunctionPointer) - * $(LREF isDelegate) + * $(LREF isInstanceOf) + * $(LREF isIterable) + * $(LREF isMutable) * $(LREF isSomeFunction) - * $(LREF isCallable) - * $(LREF isAbstractFunction) - * $(LREF isFinalFunction) - * $(LREF isAbstractClass) - * $(LREF isFinalClass) + * $(LREF isTypeTuple) * )) * $(TR $(TD General Types) $(TD - * $(LREF Unqual) * $(LREF ForeachType) - * $(LREF OriginalType) - * $(LREF PointerTarget) * $(LREF KeyType) - * $(LREF ValueType) - * $(LREF Unsigned) * $(LREF Largest) - * $(LREF Signed) - * $(LREF unsigned) * $(LREF mostNegative) + * $(LREF OriginalType) + * $(LREF PointerTarget) + * $(LREF Signed) + * $(LREF Unqual) + * $(LREF Unsigned) + * $(LREF ValueType) * )) * $(TR $(TD Misc) $(TD * $(LREF mangledName) @@ -126,12 +126,13 @@ * $(LREF select) * )) * ) + * ) * * Macros: * WIKI = Phobos/StdTraits * * Copyright: Copyright Digital Mars 2005 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright), * Tomasz Stachowiak ($(D isExpressionTuple)), * $(WEB erdani.org, Andrei Alexandrescu), @@ -148,10 +149,8 @@ * http://www.boost.org/LICENSE_1_0.txt) */ module std.traits; -import std.typetuple; -// FIXME @@@bug@@@ 12961 -import std.conv; +import std.typetuple; /////////////////////////////////////////////////////////////////////////////// // Functions @@ -300,11 +299,6 @@ private alias parentOf(alias sym : T!Args, alias T, Args...) = Identity!(__trait /** * Get the full package name for the given symbol. - * Example: - * --- - * import std.traits; - * static assert(packageName!packageName == "std"); - * --- */ template packageName(alias T) { @@ -323,15 +317,22 @@ template packageName(alias T) static assert(false, T.stringof ~ " has no parent"); } +/// +unittest +{ + import std.traits; + static assert(packageName!packageName == "std"); +} + unittest { - import std.algorithm; + import std.array; // Commented out because of dmd @@@BUG8922@@@ // static assert(packageName!std == "std"); // this package (currently: "std.std") static assert(packageName!(std.traits) == "std"); // this module static assert(packageName!packageName == "std"); // symbol in this module - static assert(packageName!(std.algorithm) == "std"); // other module from same package + static assert(packageName!(std.array) == "std"); // other module from same package import core.sync.barrier; // local import static assert(packageName!core == "core"); @@ -352,11 +353,6 @@ version (none) version(unittest) //Please uncomment me when changing packageName /** * Get the module name (including package) for the given symbol. - * Example: - * --- - * import std.traits; - * static assert(moduleName!moduleName == "std.traits"); - * --- */ template moduleName(alias T) { @@ -377,15 +373,22 @@ template moduleName(alias T) alias moduleName = moduleName!(parentOf!T); // If you use enum, it will cause compiler ICE } +/// unittest { - import std.algorithm; + import std.traits; + static assert(moduleName!moduleName == "std.traits"); +} + +unittest +{ + import std.array; static assert(!__traits(compiles, moduleName!std)); static assert(moduleName!(std.traits) == "std.traits"); // this module static assert(moduleName!moduleName == "std.traits"); // symbol in this module - static assert(moduleName!(std.algorithm) == "std.algorithm"); // other module - static assert(moduleName!(std.algorithm.map) == "std.algorithm"); // symbol in other module + static assert(moduleName!(std.array) == "std.array"); // other module + static assert(moduleName!(std.array.array) == "std.array"); // symbol in other module import core.sync.barrier; // local import static assert(!__traits(compiles, moduleName!(core.sync))); @@ -406,15 +409,14 @@ version (none) version(unittest) //Please uncomment me when changing moduleName /*** * Get the fully qualified name of a type or a symbol. Can act as an intelligent type/symbol to string converter. - * Example: - * --- - * module mymodule; - * import std.traits; - * struct MyStruct {} - * static assert(fullyQualifiedName!(const MyStruct[]) == "const(mymodule.MyStruct[])"); - * static assert(fullyQualifiedName!fullyQualifiedName == "std.traits.fullyQualifiedName"); - * --- - */ + +Example: +----------------- +module myModule; +struct MyStruct {} +static assert(fullyQualifiedName!(const MyStruct[]) == "const(myModule.MyStruct[])"); +----------------- +*/ template fullyQualifiedName(T...) if (T.length == 1) { @@ -425,6 +427,12 @@ template fullyQualifiedName(T...) enum fullyQualifiedName = fqnSym!(T[0]); } +/// +unittest +{ + static assert(fullyQualifiedName!fullyQualifiedName == "std.traits.fullyQualifiedName"); +} + version(unittest) { // Used for both fqnType and fqnSym unittests @@ -435,7 +443,7 @@ version(unittest) } ref const(Inner[string]) func( ref Inner var1, lazy scope string var2 ); - inout Inner inoutFunc(inout Inner); + Inner inoutFunc(inout Inner) inout; shared(const(Inner[string])[]) data; const Inner delegate(double, string) @safe nothrow deleg; inout(int) delegate(inout int) inout inoutDeleg; @@ -537,7 +545,7 @@ unittest private template fqnType(T, bool alreadyConst, bool alreadyImmutable, bool alreadyShared, bool alreadyInout) { - import std.string; + import std.format : format; // Convenience tags enum { @@ -564,8 +572,6 @@ private template fqnType(T, string parametersTypeString(T)() @property { - import std.array, std.algorithm, std.range; - alias parameters = ParameterTypeTuple!(T); alias parameterStC = ParameterStorageClassTuple!(T); @@ -583,6 +589,9 @@ private template fqnType(T, static if (parameters.length) { + import std.algorithm : map; + import std.range : join, zip; + string result = join( map!(a => format("%s%s", a[0], a[1]))( zip([staticMap!(storageClassesString, parameterStC)], @@ -742,7 +751,7 @@ private template fqnType(T, unittest { - import std.string : format; + import std.format : format; alias fqn = fullyQualifiedName; // Verify those 2 are the same for simple case @@ -807,12 +816,6 @@ unittest * or a class with an $(D opCall). Please note that $(D_KEYWORD ref) * is not part of a type, but the attribute of the function * (see template $(LREF functionAttributes)). - * Example: - * --- - * import std.traits; - * int foo(); - * ReturnType!foo x; // x is declared as int - * --- */ template ReturnType(func...) if (func.length == 1 && isCallable!func) @@ -823,6 +826,13 @@ template ReturnType(func...) static assert(0, "argument has no return type"); } +/// +unittest +{ + int foo(); + ReturnType!foo x; // x is declared as int +} + unittest { struct G @@ -865,14 +875,6 @@ unittest Get, as a tuple, the types of the parameters to a function, a pointer to function, a delegate, a struct with an $(D opCall), a pointer to a struct with an $(D opCall), or a class with an $(D opCall). - -Example: ---- -import std.traits; -int foo(int, long); -void bar(ParameterTypeTuple!foo); // declares void bar(int, long); -void abc(ParameterTypeTuple!foo[1]); // declares void abc(long); ---- */ template ParameterTypeTuple(func...) if (func.length == 1 && isCallable!func) @@ -883,6 +885,14 @@ template ParameterTypeTuple(func...) static assert(0, "argument has no parameters"); } +/// +unittest +{ + int foo(int, long); + void bar(ParameterTypeTuple!foo); // declares void bar(int, long); + void abc(ParameterTypeTuple!foo[1]); // declares void abc(long); +} + unittest { int foo(int i, bool b) { return 0; } @@ -910,21 +920,14 @@ unittest /** Returns the number of arguments of function $(D func). arity is undefined for variadic functions. - -Example: ---- -void foo(){} -static assert(arity!foo==0); -void bar(uint){} -static assert(arity!bar==1); ---- - */ +*/ template arity(alias func) if ( isCallable!func && variadicFunctionStyle!func == Variadic.no ) { enum size_t arity = ParameterTypeTuple!func.length; } +/// unittest { void foo(){} static assert(arity!foo==0); @@ -937,20 +940,6 @@ unittest { /** Returns a tuple consisting of the storage classes of the parameters of a function $(D func). - -Example: --------------------- -alias STC = ParameterStorageClass; // shorten the enum name - -void func(ref int ctx, out real result, real param) -{ -} -alias pstc = ParameterStorageClassTuple!func; -static assert(pstc.length == 3); // three parameters -static assert(pstc[0] == STC.ref_); -static assert(pstc[1] == STC.out_); -static assert(pstc[2] == STC.none); --------------------- */ enum ParameterStorageClass : uint { @@ -1004,6 +993,21 @@ template ParameterStorageClassTuple(func...) alias ParameterStorageClassTuple = demangleNextParameter!margs; } +/// +unittest +{ + alias STC = ParameterStorageClass; // shorten the enum name + + void func(ref int ctx, out real result, real param) + { + } + alias pstc = ParameterStorageClassTuple!func; + static assert(pstc.length == 3); // three parameters + static assert(pstc[0] == STC.ref_); + static assert(pstc[1] == STC.out_); + static assert(pstc[2] == STC.none); +} + unittest { alias STC = ParameterStorageClass; @@ -1055,7 +1059,9 @@ template ParameterIdentifierTuple(func...) { template Get(size_t i) { - static if (!isFunctionPointer!func && !isDelegate!func) + static if (!isFunctionPointer!func && !isDelegate!func + // Unnamed parameters yield CT error. + && is(typeof(__traits(identifier, PT[i..i+1]))x)) { enum Get = __traits(identifier, PT[i..i+1]); } @@ -1088,9 +1094,8 @@ template ParameterIdentifierTuple(func...) /// unittest { - import std.traits; - int foo(int num, string name); - static assert([ParameterIdentifierTuple!foo] == ["num", "name"]); + int foo(int num, string name, int); + static assert([ParameterIdentifierTuple!foo] == ["num", "name", ""]); } unittest @@ -1141,7 +1146,11 @@ template ParameterDefaultValueTuple(func...) { template Get(size_t i) { - enum get = (PT[i..i+1] args) => args[0]; + enum ParamName = ParameterIdentifierTuple!(func[0])[i]; + static if (ParamName.length) + enum get = (PT[i..i+1]) => mixin(ParamName); + else // Unnamed parameter + enum get = (PT[i..i+1] __args) => __args[0]; static if (is(typeof(get()))) enum Get = get(); else @@ -1179,8 +1188,7 @@ template ParameterDefaultValueTuple(func...) /// unittest { - import std.traits; - int foo(int num, string name = "hello", int[] arr = [1,2,3]); + int foo(int num, string name = "hello", int[] = [1,2,3]); static assert(is(ParameterDefaultValueTuple!foo[0] == void)); static assert( ParameterDefaultValueTuple!foo[1] == "hello"); static assert( ParameterDefaultValueTuple!foo[2] == [1,2,3]); @@ -1285,7 +1293,7 @@ unittest int sharedF() shared { return 0; } int x; - ref int refF() { return x; } + ref int refF() return { return x; } int propertyF() @property { return 0; } int nothrowF() nothrow { return 0; } int nogcF() @nogc { return 0; } @@ -1411,17 +1419,6 @@ private FunctionAttribute extractAttribFlags(Attribs...)() /** $(D true) if $(D func) is $(D @safe) or $(D @trusted). - -Example: --------------------- -@safe int add(int a, int b) {return a+b;} -@trusted int sub(int a, int b) {return a-b;} -@system int mul(int a, int b) {return a*b;} - -static assert( isSafe!add); -static assert( isSafe!sub); -static assert(!isSafe!mul); --------------------- */ template isSafe(alias func) if(isCallable!func) @@ -1430,7 +1427,7 @@ template isSafe(alias func) (functionAttributes!func & FunctionAttribute.trusted) != 0; } -//Verify Examples. +/// unittest { @safe int add(int a, int b) {return a+b;} @@ -1501,24 +1498,13 @@ unittest /** $(D true) if $(D func) is $(D @system). - -Example: --------------------- -@safe int add(int a, int b) {return a+b;} -@trusted int sub(int a, int b) {return a-b;} -@system int mul(int a, int b) {return a*b;} - -static assert(!isUnsafe!add); -static assert(!isUnsafe!sub); -static assert( isUnsafe!mul); --------------------- - */ +*/ template isUnsafe(alias func) { enum isUnsafe = !isSafe!func; } -//Verify Examples. +/// unittest { @safe int add(int a, int b) {return a+b;} @@ -1594,8 +1580,8 @@ instead. This will be removed in June 2015.) $(D true) all functions are $(D isSafe). -Example: --------------------- +Example +------------- @safe int add(int a, int b) {return a+b;} @trusted int sub(int a, int b) {return a-b;} @system int mul(int a, int b) {return a*b;} @@ -1603,8 +1589,8 @@ Example: static assert( areAllSafe!(add, add)); static assert( areAllSafe!(add, sub)); static assert(!areAllSafe!(sub, mul)); --------------------- - */ +------------- +*/ deprecated("Please use allSatisfy(isSafe, ...) instead.") template areAllSafe(funcs...) if (funcs.length > 0) @@ -1623,7 +1609,7 @@ template areAllSafe(funcs...) } } -//Verify Example +// Verify Example deprecated unittest { @safe int add(int a, int b) {return a+b;} @@ -1651,17 +1637,7 @@ deprecated unittest /** Returns the calling convention of function as a string. - -Example: --------------------- -string a = functionLinkage!(writeln!(string, int)); -assert(a == "D"); // extern(D) - -auto fp = &printf; -string b = functionLinkage!fp; -assert(b == "C"); // extern(C) --------------------- - */ +*/ template functionLinkage(func...) if (func.length == 1 && isCallable!func) { @@ -1677,6 +1653,19 @@ template functionLinkage(func...) ][ mangledName!Func[0] ]; } +/// +unittest +{ + import std.stdio : writeln, printf; + + string a = functionLinkage!(writeln!(string, int)); + assert(a == "D"); // extern(D) + + auto fp = &printf; + string b = functionLinkage!fp; + assert(b == "C"); // extern(C) +} + unittest { extern(D) void Dfunc() {} @@ -1698,15 +1687,6 @@ unittest /** Determines what kind of variadic parameters function has. - -Example: --------------------- -void func() {} -static assert(variadicFunctionStyle!func == Variadic.no); - -extern(C) int printf(in char*, ...); -static assert(variadicFunctionStyle!printf == Variadic.c); --------------------- */ enum Variadic { @@ -1738,6 +1718,16 @@ template variadicFunctionStyle(func...) Variadic.no; // 'Z' } +/// +unittest +{ + void func() {} + static assert(variadicFunctionStyle!func == Variadic.no); + + extern(C) int printf(in char*, ...); + static assert(variadicFunctionStyle!printf == Variadic.c); +} + unittest { import core.vararg; @@ -1762,14 +1752,6 @@ Get the function type from a callable object $(D func). Using builtin $(D typeof) on a property function yields the types of the property value, not of the property function itself. Still, $(D FunctionTypeOf) is able to obtain function types of properties. --------------------- -class C -{ - int value() @property; -} -static assert(is( typeof(C.value) == int )); -static assert(is( FunctionTypeOf!(C.value) == function )); --------------------- Note: Do not confuse function types with function pointer types; function types are @@ -1805,6 +1787,17 @@ template FunctionTypeOf(func...) static assert(0); } +/// +unittest +{ + class C + { + int value() @property { return 0; } + } + static assert(is( typeof(C.value) == int )); + static assert(is( FunctionTypeOf!(C.value) == function )); +} + unittest { int test(int a) { return 0; } @@ -1868,20 +1861,6 @@ unittest * T = The base type. * linkage = The desired linkage of the result type. * attrs = The desired $(LREF FunctionAttribute)s of the result type. - * - * Examples: - * --- - * alias ExternC(T) = SetFunctionAttributes!(T, "C", functionAttributes!T); - * --- - * - * --- - * auto assumePure(T)(T t) - * if (isFunctionPointer!T || isDelegate!T) - * { - * enum attrs = functionAttributes!T | FunctionAttribute.pure_; - * return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; - * } - * --- */ template SetFunctionAttributes(T, string linkage, uint attrs) if (isFunctionPointer!T || isDelegate!T) @@ -1965,6 +1944,19 @@ template SetFunctionAttributes(T, string linkage, uint attrs) alias SetFunctionAttributes = FunctionTypeOf!(SetFunctionAttributes!(T*, linkage, attrs)); } +/// +unittest +{ + alias ExternC(T) = SetFunctionAttributes!(T, "C", functionAttributes!T); + + auto assumePure(T)(T t) + if (isFunctionPointer!T || isDelegate!T) + { + enum attrs = functionAttributes!T | FunctionAttribute.pure_; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; + } +} + version (unittest) { // Some function types to test. @@ -1983,7 +1975,7 @@ unittest typeof(&dstyle), typeof(&typesafe))) { foreach (T; TypeTuple!(BaseT, FunctionTypeOf!BaseT)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 enum linkage = functionLinkage!T; enum attrs = functionAttributes!T; @@ -2016,7 +2008,7 @@ unittest // Strip all attributes again. alias T3 = SetFunctionAttributes!(T2, functionLinkage!T, FA.none); static assert(is(T3 == T)); - } + }(); } } @@ -2173,100 +2165,60 @@ unittest } -// // FieldOffsetsTuple -// private template FieldOffsetsTupleImpl(size_t n, T...) -// { -// static if (T.length == 0) -// { -// alias TypeTuple!() Result; -// } -// else -// { -// //private alias FieldTypeTuple!(T[0]) Types; -// private enum size_t myOffset = -// ((n + T[0].alignof - 1) / T[0].alignof) * T[0].alignof; -// static if (is(T[0] == struct)) -// { -// alias FieldTypeTuple!(T[0]) MyRep; -// alias FieldOffsetsTupleImpl!(myOffset, MyRep, T[1 .. $]).Result -// Result; -// } -// else -// { -// private enum size_t mySize = T[0].sizeof; -// alias TypeTuple!myOffset Head; -// static if (is(T == union)) -// { -// alias FieldOffsetsTupleImpl!(myOffset, T[1 .. $]).Result -// Tail; -// } -// else -// { -// alias FieldOffsetsTupleImpl!(myOffset + mySize, -// T[1 .. $]).Result -// Tail; -// } -// alias TypeTuple!(Head, Tail) Result; -// } -// } -// } - -// template FieldOffsetsTuple(T...) -// { -// alias FieldOffsetsTupleImpl!(0, T).Result FieldOffsetsTuple; -// } - -// unittest -// { -// alias T1 = FieldOffsetsTuple!int; -// assert(T1.length == 1 && T1[0] == 0); -// // -// struct S2 { char a; int b; char c; double d; char e, f; } -// alias T2 = FieldOffsetsTuple!S2; -// //pragma(msg, T2); -// static assert(T2.length == 6 -// && T2[0] == 0 && T2[1] == 4 && T2[2] == 8 && T2[3] == 16 -// && T2[4] == 24&& T2[5] == 25); -// // -// class C { int a, b, c, d; } -// struct S3 { char a; C b; char c; } -// alias T3 = FieldOffsetsTuple!S3; -// //pragma(msg, T2); -// static assert(T3.length == 3 -// && T3[0] == 0 && T3[1] == 4 && T3[2] == 8); -// // -// struct S4 { char a; union { int b; char c; } int d; } -// alias T4 = FieldOffsetsTuple!S4; -// //pragma(msg, FieldTypeTuple!S4); -// static assert(T4.length == 4 -// && T4[0] == 0 && T4[1] == 4 && T4[2] == 8); -// } - -// /*** -// Get the offsets of the fields of a struct or class. -// */ - -// template FieldOffsetsTuple(S) -// { -// static if (is(S == struct) || is(S == class)) -// alias typeof(S.tupleof) FieldTypeTuple; -// else -// static assert(0, "argument is not struct or class"); -// } - +//Required for FieldNameTuple +private enum NameOf(alias T) = T.stringof; + +/** + * Get as an expression tuple the names of the fields of a struct, class, or + * union. This consists of the fields that take up memory space, excluding the + * hidden fields like the virtual function table pointer or a context pointer + * for nested types. If $(D T) isn't a struct, class, or union returns an + * expression tuple with an empty string. + */ +template FieldNameTuple(T) +{ + static if (is(T == struct) || is(T == union)) + alias FieldNameTuple = staticMap!(NameOf, T.tupleof[0 .. $ - isNested!T]); + else static if (is(T == class)) + alias FieldNameTuple = staticMap!(NameOf, T.tupleof); + else + alias FieldNameTuple = TypeTuple!""; +} + +/// +unittest +{ + struct S { int x; float y; } + static assert(FieldNameTuple!S == TypeTuple!("x", "y")); + static assert(FieldNameTuple!int == TypeTuple!""); +} + +unittest +{ + static assert(FieldNameTuple!int == TypeTuple!""); + + static struct StaticStruct1 { } + static assert(is(FieldNameTuple!StaticStruct1 == TypeTuple!())); + + static struct StaticStruct2 { int a, b; } + static assert(FieldNameTuple!StaticStruct2 == TypeTuple!("a", "b")); + + int i; + + struct NestedStruct1 { void f() { ++i; } } + static assert(is(FieldNameTuple!NestedStruct1 == TypeTuple!())); + + struct NestedStruct2 { int a; void f() { ++i; } } + static assert(FieldNameTuple!NestedStruct2 == TypeTuple!"a"); + + class NestedClass { int a; void f() { ++i; } } + static assert(FieldNameTuple!NestedClass == TypeTuple!"a"); +} + + /*** Get the primitive types of the fields of a struct or class, in topological order. - -Example: ----- -struct S1 { int a; float b; } -struct S2 { char[] a; union { S1 b; S1 * c; } } -alias R = RepresentationTypeTuple!S2; -assert(R.length == 4 - && is(R[0] == char[]) && is(R[1] == int) - && is(R[2] == float) && is(R[3] == S1*)); ----- */ template RepresentationTypeTuple(T) { @@ -2291,10 +2243,6 @@ template RepresentationTypeTuple(T) // RepresentationTypes; alias Impl = Impl!(FieldTypeTuple!(T[0]), T[1 .. $]); } - else static if (is(T[0] U == typedef)) - { - alias Impl = Impl!(FieldTypeTuple!U, T[1 .. $]); - } else { alias Impl = TypeTuple!(T[0], Impl!(T[1 .. $])); @@ -2306,16 +2254,23 @@ template RepresentationTypeTuple(T) { alias RepresentationTypeTuple = Impl!(FieldTypeTuple!T); } - else static if (is(T U == typedef)) - { - alias RepresentationTypeTuple = RepresentationTypeTuple!U; - } else { alias RepresentationTypeTuple = Impl!T; } } +/// +unittest +{ + struct S1 { int a; float b; } + struct S2 { char[] a; union { S1 b; S1 * c; } } + alias R = RepresentationTypeTuple!S2; + assert(R.length == 4 + && is(R[0] == char[]) && is(R[1] == int) + && is(R[2] == float) && is(R[3] == S1*)); +} + unittest { alias S1 = RepresentationTypeTuple!int; @@ -2347,90 +2302,11 @@ unittest static assert(R2.length == 2 && is(R2[0] == int) && is(R2[1] == immutable(Object))); } -/* -RepresentationOffsets -*/ - -// private template Repeat(size_t n, T...) -// { -// static if (n == 0) alias TypeTuple!() Repeat; -// else alias TypeTuple!(T, Repeat!(n - 1, T)) Repeat; -// } - -// template RepresentationOffsetsImpl(size_t n, T...) -// { -// static if (T.length == 0) -// { -// alias TypeTuple!() Result; -// } -// else -// { -// private enum size_t myOffset = -// ((n + T[0].alignof - 1) / T[0].alignof) * T[0].alignof; -// static if (!is(T[0] == union)) -// { -// alias Repeat!(n, FieldTypeTuple!(T[0])).Result -// Head; -// } -// static if (is(T[0] == struct)) -// { -// alias .RepresentationOffsetsImpl!(n, FieldTypeTuple!(T[0])).Result -// Head; -// } -// else -// { -// alias TypeTuple!myOffset Head; -// } -// alias TypeTuple!(Head, -// RepresentationOffsetsImpl!( -// myOffset + T[0].sizeof, T[1 .. $]).Result) -// Result; -// } -// } - -// template RepresentationOffsets(T) -// { -// alias RepresentationOffsetsImpl!(0, T).Result -// RepresentationOffsets; -// } - -// unittest -// { -// struct S1 { char c; int i; } -// alias RepresentationOffsets!S1 Offsets; -// static assert(Offsets[0] == 0); -// //pragma(msg, Offsets[1]); -// static assert(Offsets[1] == 4); -// } - /* Statically evaluates to $(D true) if and only if $(D T)'s representation contains at least one field of pointer or array type. Members of class types are not considered raw pointers. Pointers to immutable objects are not considered raw aliasing. - -Example: ---- -// simple types -static assert(!hasRawAliasing!int); -static assert( hasRawAliasing!(char*)); -// references aren't raw pointers -static assert(!hasRawAliasing!Object); -// built-in arrays do contain raw pointers -static assert( hasRawAliasing!(int[])); -// aggregate of simple types -struct S1 { int a; double b; } -static assert(!hasRawAliasing!S1); -// indirect aggregation -struct S2 { S1 a; double b; } -static assert(!hasRawAliasing!S2); -// struct with a pointer member -struct S3 { int a; double * b; } -static assert( hasRawAliasing!S3); -// struct with an indirect pointer member -struct S4 { S3 a; double b; } -static assert( hasRawAliasing!S4); ----- */ private template hasRawAliasing(T...) { @@ -2458,26 +2334,35 @@ private template hasRawAliasing(T...) enum hasRawAliasing = Impl!(RepresentationTypeTuple!T); } +/// unittest { // simple types static assert(!hasRawAliasing!int); static assert( hasRawAliasing!(char*)); - // references aren't raw pointers static assert(!hasRawAliasing!Object); - static assert(!hasRawAliasing!int); - - struct S1 { int z; } - struct S2 { int* z; } + // built-in arrays do contain raw pointers + static assert( hasRawAliasing!(int[])); + // aggregate of simple types + struct S1 { int a; double b; } static assert(!hasRawAliasing!S1); - static assert( hasRawAliasing!S2); + // indirect aggregation + struct S2 { S1 a; double b; } + static assert(!hasRawAliasing!S2); +} - struct S3 { int a; int* z; int c; } - struct S4 { int a; int z; int c; } +unittest +{ + // struct with a pointer member + struct S3 { int a; double * b; } + static assert( hasRawAliasing!S3); + // struct with an indirect pointer member + struct S4 { S3 a; double b; } + static assert( hasRawAliasing!S4); struct S5 { int a; Object z; int c; } static assert( hasRawAliasing!S3); - static assert(!hasRawAliasing!S4); + static assert( hasRawAliasing!S4); static assert(!hasRawAliasing!S5); union S6 { int a; int b; } @@ -2500,9 +2385,6 @@ unittest static assert(!hasRawAliasing!S12); static assert( hasRawAliasing!S13); - //typedef int* S8; - //static assert(hasRawAliasing!S8); - enum S9 { a } static assert(!hasRawAliasing!S9); @@ -2521,35 +2403,6 @@ Statically evaluates to $(D true) if and only if $(D T)'s representation contains at least one non-shared field of pointer or array type. Members of class types are not considered raw pointers. Pointers to immutable objects are not considered raw aliasing. - -Example: ---- -// simple types -static assert(!hasRawUnsharedAliasing!int); -static assert( hasRawUnsharedAliasing!(char*)); -static assert(!hasRawUnsharedAliasing!(shared char*)); -// references aren't raw pointers -static assert(!hasRawUnsharedAliasing!Object); -// built-in arrays do contain raw pointers -static assert( hasRawUnsharedAliasing!(int[])); -static assert(!hasRawUnsharedAliasing!(shared int[])); -// aggregate of simple types -struct S1 { int a; double b; } -static assert(!hasRawUnsharedAliasing!S1); -// indirect aggregation -struct S2 { S1 a; double b; } -static assert(!hasRawUnsharedAliasing!S2); -// struct with a pointer member -struct S3 { int a; double * b; } -static assert( hasRawUnsharedAliasing!S3); -struct S4 { int a; shared double * b; } -static assert( hasRawUnsharedAliasing!S4); -// struct with an indirect pointer member -struct S5 { S3 a; double b; } -static assert( hasRawUnsharedAliasing!S5); -struct S6 { S4 a; double b; } -static assert(!hasRawUnsharedAliasing!S6); ----- */ private template hasRawUnsharedAliasing(T...) { @@ -2577,31 +2430,45 @@ private template hasRawUnsharedAliasing(T...) enum hasRawUnsharedAliasing = Impl!(RepresentationTypeTuple!T); } +/// unittest { // simple types static assert(!hasRawUnsharedAliasing!int); static assert( hasRawUnsharedAliasing!(char*)); static assert(!hasRawUnsharedAliasing!(shared char*)); - // references aren't raw pointers static assert(!hasRawUnsharedAliasing!Object); - static assert(!hasRawUnsharedAliasing!int); - - struct S1 { int z; } - struct S2 { int* z; } + // built-in arrays do contain raw pointers + static assert( hasRawUnsharedAliasing!(int[])); + static assert(!hasRawUnsharedAliasing!(shared int[])); + // aggregate of simple types + struct S1 { int a; double b; } static assert(!hasRawUnsharedAliasing!S1); - static assert( hasRawUnsharedAliasing!S2); - - struct S3 { shared int* z; } - struct S4 { int a; int* z; int c; } - static assert(!hasRawUnsharedAliasing!S3); - static assert( hasRawUnsharedAliasing!S4); + // indirect aggregation + struct S2 { S1 a; double b; } + static assert(!hasRawUnsharedAliasing!S2); + // struct with a pointer member + struct S3 { int a; double * b; } + static assert( hasRawUnsharedAliasing!S3); + struct S4 { int a; shared double * b; } + static assert(!hasRawUnsharedAliasing!S4); +} - struct S5 { int a; shared int* z; int c; } - struct S6 { int a; int z; int c; } +unittest +{ + // struct with a pointer member + struct S3 { int a; double * b; } + static assert( hasRawUnsharedAliasing!S3); + struct S4 { int a; shared double * b; } + static assert(!hasRawUnsharedAliasing!S4); + // struct with an indirect pointer member + struct S5 { S3 a; double b; } + static assert( hasRawUnsharedAliasing!S5); + struct S6 { S4 a; double b; } + static assert(!hasRawUnsharedAliasing!S6); struct S7 { int a; Object z; int c; } - static assert(!hasRawUnsharedAliasing!S5); + static assert( hasRawUnsharedAliasing!S5); static assert(!hasRawUnsharedAliasing!S6); static assert(!hasRawUnsharedAliasing!S7); @@ -2639,11 +2506,6 @@ unittest static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() shared const)))); static assert(!hasRawUnsharedAliasing!(void function())); - //typedef int* S11; - //typedef shared int* S12; - //static assert( hasRawUnsharedAliasing!S11); - //static assert( hasRawUnsharedAliasing!S12); - enum S13 { a } static assert(!hasRawUnsharedAliasing!S13); @@ -2729,10 +2591,6 @@ private template hasObjects(T...) { enum hasObjects = false; } - else static if (is(T[0] U == typedef)) - { - enum hasObjects = hasObjects!(U, T[1 .. $]); - } else static if (is(T[0] == struct)) { enum hasObjects = hasObjects!( @@ -2756,10 +2614,6 @@ private template hasUnsharedObjects(T...) { enum hasUnsharedObjects = false; } - else static if (is(T[0] U == typedef)) - { - enum hasUnsharedObjects = hasUnsharedObjects!(U, T[1 .. $]); - } else static if (is(T[0] == struct)) { enum hasUnsharedObjects = hasUnsharedObjects!( @@ -2781,7 +2635,6 @@ immutable;) $(LI a reference to a class or interface type $(D C) and $(D C) is not immutable.) $(LI an associative array that is not immutable.) $(LI a delegate.)) */ - template hasAliasing(T...) { import std.typecons : Rebindable; @@ -2803,6 +2656,7 @@ template hasAliasing(T...) } } +/// unittest { struct S1 { int a; Object b; } @@ -2813,7 +2667,10 @@ unittest static assert(!hasAliasing!S2); static assert(!hasAliasing!S3); static assert(!hasAliasing!S4); +} +unittest +{ static assert( hasAliasing!(uint[uint])); static assert(!hasAliasing!(immutable(uint[uint]))); static assert( hasAliasing!(void delegate())); @@ -2894,6 +2751,7 @@ template hasIndirections(T) isAssociativeArray!T || is (T == class) || is(T == interface); } +/// unittest { static assert( hasIndirections!(int[string])); @@ -2905,7 +2763,10 @@ unittest static assert(!hasIndirections!(void function())); static assert( hasIndirections!(void*[1])); static assert(!hasIndirections!(byte[1])); +} +unittest +{ // void static array hides actual type of bits, so "may have indirections". static assert( hasIndirections!(void[1])); interface I {} @@ -2978,11 +2839,6 @@ unittest //12000 A!int dummy; } -//Explicitly undocumented. They will be removed in December 2014. -deprecated("Please use hasLocalAliasing instead.") alias hasLocalAliasing = hasUnsharedAliasing; -deprecated("Please use hasRawLocalAliasing instead.") alias hasRawLocalAliasing = hasRawUnsharedAliasing; -deprecated("Please use hasLocalObjects instead.") alias hasLocalObjects = hasUnsharedObjects; - /** Returns $(D true) if and only if $(D T)'s representation includes at least one of the following: $(OL $(LI a raw pointer $(D U*) and $(D U) @@ -3024,6 +2880,7 @@ template hasUnsharedAliasing(T...) } } +/// unittest { struct S1 { int a; Object b; } @@ -3041,7 +2898,10 @@ unittest static assert( hasUnsharedAliasing!S5); static assert(!hasUnsharedAliasing!S6); static assert(!hasUnsharedAliasing!S7); +} +unittest +{ /* Issue 6642 */ import std.typecons : Rebindable; struct S8 { int a; Rebindable!(immutable Object) b; } @@ -3054,6 +2914,11 @@ unittest static assert(!hasUnsharedAliasing!(void delegate() immutable)); static assert(!hasUnsharedAliasing!(void delegate() shared)); static assert(!hasUnsharedAliasing!(void delegate() shared const)); +} + +unittest +{ + import std.typecons : Rebindable; static assert( hasUnsharedAliasing!(const(void delegate()))); static assert( hasUnsharedAliasing!(const(void delegate() const))); static assert(!hasUnsharedAliasing!(const(void delegate() immutable))); @@ -3175,6 +3040,7 @@ template hasElaborateCopyConstructor(S) } } +/// unittest { static assert(!hasElaborateCopyConstructor!int); @@ -3231,6 +3097,7 @@ template hasElaborateAssign(S) } } +/// unittest { static assert(!hasElaborateAssign!int); @@ -3247,7 +3114,11 @@ unittest static assert( hasElaborateAssign!S3); static assert( hasElaborateAssign!(S3[1])); static assert(!hasElaborateAssign!(S3[0])); +} +unittest +{ + static struct S { void opAssign(S) {} } static struct S4 { void opAssign(U)(U u) {} @@ -3311,6 +3182,7 @@ template hasElaborateDestructor(S) } } +/// unittest { static assert(!hasElaborateDestructor!int); @@ -3354,9 +3226,9 @@ template hasMember(T, string name) } } +/// unittest { - //pragma(msg, __traits(allMembers, void delegate())); static assert(!hasMember!(int, "blah")); struct S1 { int blah; } struct S2 { int blah(){ return 0; } } @@ -3556,22 +3428,7 @@ unittest * Get a $(D_PARAM TypeTuple) of the base class and base interfaces of * this class or interface. $(D_PARAM BaseTypeTuple!Object) returns * the empty type tuple. - * - * Example: - * --- - * import std.traits, std.typetuple, std.stdio; - * interface I { } - * class A { } - * class B : A, I { } - * - * void main() - * { - * alias TL = BaseTypeTuple!B; - * writeln(typeid(TL)); // prints: (A,I) - * } - * --- */ - template BaseTypeTuple(A) { static if (is(A P == super)) @@ -3580,6 +3437,7 @@ template BaseTypeTuple(A) static assert(0, "argument is not a class or interface"); } +/// unittest { interface I1 { } @@ -3612,23 +3470,7 @@ unittest * Get a $(D_PARAM TypeTuple) of $(I all) base classes of this class, * in decreasing order. Interfaces are not included. $(D_PARAM * BaseClassesTuple!Object) yields the empty type tuple. - * - * Example: - * --- - * import std.traits, std.typetuple, std.stdio; - * interface I { } - * class A { } - * class B : A, I { } - * class C : B { } - * - * void main() - * { - * alias TL = BaseClassesTuple!C; - * writeln(typeid(TL)); // prints: (B,A,Object) - * } - * --- */ - template BaseClassesTuple(T) if (is(T == class)) { @@ -3648,6 +3490,7 @@ template BaseClassesTuple(T) } } +/// unittest { class C1 { } @@ -3658,6 +3501,10 @@ unittest static assert(is(BaseClassesTuple!C2 == TypeTuple!(C1, Object))); static assert(is(BaseClassesTuple!C3 == TypeTuple!(C2, C1, Object))); static assert(!BaseClassesTuple!Object.length); +} + +unittest +{ struct S { } static assert(!__traits(compiles, BaseClassesTuple!S)); interface I { } @@ -3672,24 +3519,7 @@ unittest * indirectly inherited by this class or interface. Interfaces do not * repeat if multiply implemented. $(D_PARAM InterfacesTuple!Object) * yields the empty type tuple. - * - * Example: - * --- - * import std.traits, std.typetuple, std.stdio; - * interface I1 { } - * interface I2 { } - * class A : I1, I2 { } - * class B : A, I1 { } - * class C : B { } - * - * void main() - * { - * alias TL = InterfacesTuple!C; - * writeln(typeid(TL)); // prints: (I1, I2) - * } - * --- */ - template InterfacesTuple(T) { template Flatten(H, T...) @@ -3715,32 +3545,33 @@ template InterfacesTuple(T) unittest { - { - // doc example - interface I1 {} - interface I2 {} - class A : I1, I2 { } - class B : A, I1 { } - class C : B { } - alias TL = InterfacesTuple!C; - static assert(is(TL[0] == I1) && is(TL[1] == I2)); - } - { - interface Iaa {} - interface Iab {} - interface Iba {} - interface Ibb {} - interface Ia : Iaa, Iab {} - interface Ib : Iba, Ibb {} - interface I : Ia, Ib {} - interface J {} - class B2 : J {} - class C2 : B2, Ia, Ib {} - static assert(is(InterfacesTuple!I == - TypeTuple!(Ia, Iaa, Iab, Ib, Iba, Ibb))); - static assert(is(InterfacesTuple!C2 == - TypeTuple!(J, Ia, Iaa, Iab, Ib, Iba, Ibb))); - } + // doc example + interface I1 {} + interface I2 {} + class A : I1, I2 { } + class B : A, I1 { } + class C : B { } + alias TL = InterfacesTuple!C; + static assert(is(TL[0] == I1) && is(TL[1] == I2)); +} + +unittest +{ + interface Iaa {} + interface Iab {} + interface Iba {} + interface Ibb {} + interface Ia : Iaa, Iab {} + interface Ib : Iba, Ibb {} + interface I : Ia, Ib {} + interface J {} + class B2 : J {} + class C2 : B2, Ia, Ib {} + static assert(is(InterfacesTuple!I == + TypeTuple!(Ia, Iaa, Iab, Ib, Iba, Ibb))); + static assert(is(InterfacesTuple!C2 == + TypeTuple!(J, Ia, Iaa, Iab, Ib, Iba, Ibb))); + } /** @@ -3748,23 +3579,7 @@ unittest * T), in decreasing order, followed by $(D_PARAM T)'s * interfaces. $(D_PARAM TransitiveBaseTypeTuple!Object) yields the * empty type tuple. - * - * Example: - * --- - * import std.traits, std.typetuple, std.stdio; - * interface I { } - * class A { } - * class B : A, I { } - * class C : B { } - * - * void main() - * { - * alias TL = TransitiveBaseTypeTuple!C; - * writeln(typeid(TL)); // prints: (B,A,Object,I) - * } - * --- */ - template TransitiveBaseTypeTuple(T) { static if (is(T == Object)) @@ -3774,6 +3589,7 @@ template TransitiveBaseTypeTuple(T) TypeTuple!(BaseClassesTuple!T, InterfacesTuple!T); } +/// unittest { interface J1 {} @@ -3797,23 +3613,6 @@ unittest Returns a tuple of non-static functions with the name $(D name) declared in the class or interface $(D C). Covariant duplicates are shrunk into the most derived one. - -Example: --------------------- -interface I { I foo(); } -class B -{ - real foo(real v) { return v; } -} -class C : B, I -{ - override C foo() { return this; } // covariant overriding of I.foo() -} -alias MemberFunctionsTuple!(C, "foo") foos; -static assert(foos.length == 2); -static assert(__traits(isSame, foos[0], C.foo)); -static assert(__traits(isSame, foos[1], B.foo)); --------------------- */ template MemberFunctionsTuple(C, string name) if (is(C == class) || is(C == interface)) @@ -3905,6 +3704,24 @@ template MemberFunctionsTuple(C, string name) alias TypeTuple!() MemberFunctionsTuple; } +/// +unittest +{ + interface I { I foo(); } + class B + { + real foo(real v) { return v; } + } + class C : B, I + { + override C foo() { return this; } // covariant overriding of I.foo() + } + alias MemberFunctionsTuple!(C, "foo") foos; + static assert(foos.length == 2); + static assert(__traits(isSame, foos[0], C.foo)); + static assert(__traits(isSame, foos[1], B.foo)); +} + unittest { interface I { I test(); } @@ -3948,12 +3765,6 @@ unittest /** Returns an alias to the template that $(D T) is an instance of. - -Example: --------------------- -struct Foo(T, U) {} -static assert(__traits(isSame, TemplateOf!(Foo!(int, real)), Foo)); --------------------- */ template TemplateOf(alias T : Base!Args, alias Base, Args...) { @@ -3965,6 +3776,13 @@ template TemplateOf(T : Base!Args, alias Base, Args...) alias TemplateOf = Base; } +/// +unittest +{ + struct Foo(T, U) {} + static assert(__traits(isSame, TemplateOf!(Foo!(int, real)), Foo)); +} + unittest { template Foo1(A) {} @@ -3991,23 +3809,25 @@ unittest /** Returns a $(D TypeTuple) of the template arguments used to instantiate $(D T). - -Example: --------------------- -struct Foo(T, U) {} -static assert(is(TemplateArgsOf!(Foo!(int, real)) == TypeTuple!(int, real))); --------------------- */ template TemplateArgsOf(alias T : Base!Args, alias Base, Args...) { alias TemplateArgsOf = Args; } +/// ditto template TemplateArgsOf(T : Base!Args, alias Base, Args...) { alias TemplateArgsOf = Args; } +/// +unittest +{ + struct Foo(T, U) {} + static assert(is(TemplateArgsOf!(Foo!(int, real)) == TypeTuple!(int, real))); +} + unittest { template Foo1(A) {} @@ -4445,26 +4265,6 @@ unittest /** Determines whether the function type $(D F) is covariant with $(D G), i.e., functions of the type $(D F) can override ones of the type $(D G). - -Example: --------------------- -interface I { I clone(); } -interface J { J clone(); } -class C : I -{ - override C clone() // covariant overriding of I.clone() - { - return new C; - } -} - -// C.clone() can override I.clone(), indeed. -static assert(isCovariantWith!(typeof(C.clone), typeof(I.clone))); - -// C.clone() can't override J.clone(); the return type C is not implicitly -// convertible to J. -static assert(isCovariantWith!(typeof(C.clone), typeof(J.clone))); --------------------- */ template isCovariantWith(F, G) if (is(F == function) && is(G == function)) @@ -4574,6 +4374,27 @@ template isCovariantWith(F, G) } } +/// +unittest +{ + interface I { I clone(); } + interface J { J clone(); } + class C : I + { + override C clone() // covariant overriding of I.clone() + { + return new C; + } + } + + // C.clone() can override I.clone(), indeed. + static assert(isCovariantWith!(typeof(C.clone), typeof(I.clone))); + + // C.clone() can't override J.clone(); the return type C is not implicitly + // convertible to J. + static assert(!isCovariantWith!(typeof(C.clone), typeof(J.clone))); +} + unittest { enum bool isCovariantWith(alias f, alias g) = .isCovariantWith!(typeof(f), typeof(g)); @@ -5113,6 +4934,7 @@ template BuiltinTypeOf(T) */ enum bool isBoolean(T) = is(BooleanTypeOf!T) && !isAggregateType!T; +/// unittest { static assert( isBoolean!bool); @@ -5195,6 +5017,7 @@ Detect whether $(D T) is a scalar type (a built-in numeric, character or boolean */ enum bool isScalarType(T) = isNumeric!T || isSomeChar!T || isBoolean!T; +/// unittest { static assert(!isScalarType!void); @@ -5209,6 +5032,7 @@ Detect whether $(D T) is a basic type (scalar type or void). */ enum bool isBasicType(T) = isScalarType!T || is(T == void); +/// unittest { static assert(isBasicType!void); @@ -5257,6 +5081,17 @@ Detect whether $(D T) is one of the built-in character types. */ enum bool isSomeChar(T) = is(CharTypeOf!T) && !isAggregateType!T; +/// +unittest +{ + static assert(!isSomeChar!int); + static assert(!isSomeChar!byte); + static assert(!isSomeChar!string); + static assert(!isSomeChar!wstring); + static assert(!isSomeChar!dstring); + static assert(!isSomeChar!(char[4])); +} + unittest { enum EC : char { a = 'x', b = 'y' } @@ -5269,13 +5104,6 @@ unittest static assert(!isSomeChar!( SubTypeOf!(Q!T) )); } } - - static assert(!isSomeChar!int); - static assert(!isSomeChar!byte); - static assert(!isSomeChar!string); - static assert(!isSomeChar!wstring); - static assert(!isSomeChar!dstring); - static assert(!isSomeChar!(char[4])); } /** @@ -5284,19 +5112,14 @@ Detect whether $(D T) is one of the built-in string types. The built-in string types are $(D Char[]), where $(D Char) is any of $(D char), $(D wchar) or $(D dchar), with or without qualifiers. -Static arrays of characters (like $(D char[80]) are not considered +Static arrays of characters (like $(D char[80])) are not considered built-in string types. */ enum bool isSomeString(T) = is(StringTypeOf!T) && !isAggregateType!T && !isStaticArray!T; +/// unittest { - foreach (T; TypeTuple!(char[], dchar[], string, wstring, dstring)) - { - static assert( isSomeString!( T )); - static assert(!isSomeString!(SubTypeOf!(T))); - } - static assert(!isSomeString!int); static assert(!isSomeString!(int[])); static assert(!isSomeString!(byte[])); @@ -5307,6 +5130,15 @@ unittest static assert( isSomeString!ES); } +unittest +{ + foreach (T; TypeTuple!(char[], dchar[], string, wstring, dstring)) + { + static assert( isSomeString!( T )); + static assert(!isSomeString!(SubTypeOf!(T))); + } +} + enum bool isNarrowString(T) = (is(T : const char[]) || is(T : const wchar[])) && !isAggregateType!T && !isStaticArray!T; unittest @@ -5335,6 +5167,19 @@ unittest */ enum bool isStaticArray(T) = is(StaticArrayTypeOf!T) && !isAggregateType!T; +/// +unittest +{ + static assert(!isStaticArray!(const(int)[])); + static assert(!isStaticArray!(immutable(int)[])); + static assert(!isStaticArray!(const(int)[4][])); + static assert(!isStaticArray!(int[])); + static assert(!isStaticArray!(int[char])); + static assert(!isStaticArray!(int[1][])); + static assert(!isStaticArray!(int[int])); + static assert(!isStaticArray!int); +} + unittest { foreach (T; TypeTuple!(int[51], int[][2], @@ -5348,15 +5193,6 @@ unittest } } - static assert(!isStaticArray!(const(int)[])); - static assert(!isStaticArray!(immutable(int)[])); - static assert(!isStaticArray!(const(int)[4][])); - static assert(!isStaticArray!(int[])); - static assert(!isStaticArray!(int[char])); - static assert(!isStaticArray!(int[1][])); - static assert(!isStaticArray!(int[int])); - static assert(!isStaticArray!int); - //enum ESA : int[1] { a = [1], b = [2] } //static assert( isStaticArray!ESA); } @@ -5516,6 +5352,7 @@ enum bool isAggregateType(T) = is(T == struct) || is(T == union) || */ enum bool isIterable(T) = is(typeof({ foreach(elem; T.init) {} })); +/// unittest { struct OpApply @@ -5544,6 +5381,7 @@ unittest */ enum bool isMutable(T) = !is(T == const) && !is(T == immutable) && !is(T == inout); +/// unittest { static assert( isMutable!int); @@ -5563,6 +5401,7 @@ unittest */ enum bool isInstanceOf(alias S, T) = is(T == S!Args, Args...); +/// unittest { static struct Foo(T...) { } @@ -5579,7 +5418,9 @@ unittest /** * Check whether the tuple T is an expression tuple. - * An expression tuple only contains expressions. See also $(LREF isTypeTuple). + * An expression tuple only contains expressions. + * + * See_Also: $(LREF isTypeTuple). */ template isExpressionTuple(T ...) { @@ -5624,7 +5465,9 @@ unittest /** * Check whether the tuple $(D T) is a type tuple. - * A type tuple only contains types. See also $(LREF isExpressionTuple). + * A type tuple only contains types. + * + * See_Also: $(LREF isExpressionTuple). */ template isTypeTuple(T...) { @@ -5680,6 +5523,7 @@ template isFunctionPointer(T...) enum bool isFunctionPointer = false; } +/// unittest { static void foo() {} @@ -5718,6 +5562,7 @@ template isDelegate(T...) enum bool isDelegate = false; } +/// unittest { static void sfunc() { } @@ -5812,6 +5657,7 @@ template isCallable(T...) enum bool isCallable = isSomeFunction!T; } +/// unittest { interface I { real value() @property; } @@ -5845,7 +5691,7 @@ unittest class AC { abstract void foo(); } static assert(!isAbstractFunction!(S.foo)); static assert(!isAbstractFunction!(C.foo)); - static assert(isAbstractFunction!(AC.foo)); + static assert( isAbstractFunction!(AC.foo)); } /** @@ -5857,6 +5703,7 @@ template isFinalFunction(T...) enum bool isFinalFunction = __traits(isFinalFunction, T[0]); } +/// unittest { struct S { void bar() { } } @@ -5867,9 +5714,9 @@ unittest final void foo(); } static assert(!isFinalFunction!(S.bar)); - static assert(isFinalFunction!(FC.foo)); + static assert( isFinalFunction!(FC.foo)); static assert(!isFinalFunction!(C.bar)); - static assert(isFinalFunction!(C.foo)); + static assert( isFinalFunction!(C.foo)); } /** @@ -5897,6 +5744,7 @@ template isAbstractClass(T...) enum bool isAbstractClass = __traits(isAbstractClass, T[0]); } +/// unittest { struct S { } @@ -5904,7 +5752,7 @@ unittest abstract class AC { } static assert(!isAbstractClass!S); static assert(!isAbstractClass!C); - static assert(isAbstractClass!AC); + static assert( isAbstractClass!AC); } /** @@ -5916,6 +5764,7 @@ template isFinalClass(T...) enum bool isFinalClass = __traits(isFinalClass, T[0]); } +/// unittest { class C { } @@ -5924,8 +5773,8 @@ unittest final class FC2 { } static assert(!isFinalClass!C); static assert(!isFinalClass!AC); - static assert(isFinalClass!FC1); - static assert(isFinalClass!FC2); + static assert( isFinalClass!FC1); + static assert( isFinalClass!FC2); } //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// @@ -5934,15 +5783,6 @@ unittest /** Removes all qualifiers, if any, from type $(D T). - -Example: ----- -static assert(is(Unqual!int == int)); -static assert(is(Unqual!(const int) == int)); -static assert(is(Unqual!(immutable int) == int)); -static assert(is(Unqual!(shared int) == int)); -static assert(is(Unqual!(shared(const int)) == int)); ----- */ template Unqual(T) { @@ -5968,6 +5808,16 @@ template Unqual(T) } } +/// +unittest +{ + static assert(is(Unqual!int == int)); + static assert(is(Unqual!(const int) == int)); + static assert(is(Unqual!(immutable int) == int)); + static assert(is(Unqual!(shared int) == int)); + static assert(is(Unqual!(shared(const int)) == int)); +} + unittest { static assert(is(Unqual!( int) == int)); @@ -6032,6 +5882,7 @@ template ForeachType(T) })); } +/// unittest { static assert(is(ForeachType!(uint[]) == uint)); @@ -6042,42 +5893,28 @@ unittest /** -Strips off all $(D typedef)s (including $(D enum) ones) from type $(D T). - -Example: --------------------- -enum E : int { a } -typedef E F; -typedef const F G; -static assert(is(OriginalType!G == const int)); --------------------- + * Strips off all $(D enum)s from type $(D T). */ template OriginalType(T) { template Impl(T) { - static if (is(T U == typedef)) alias Impl = OriginalType!U; - else static if (is(T U == enum)) alias Impl = OriginalType!U; - else alias Impl = T; + static if (is(T U == enum)) alias Impl = OriginalType!U; + else alias Impl = T; } alias OriginalType = ModifyTypePreservingSTC!(Impl, T); } +/// unittest { - //typedef real T; - //typedef T U; - //enum V : U { a } - //static assert(is(OriginalType!T == real)); - //static assert(is(OriginalType!U == real)); - //static assert(is(OriginalType!V == real)); enum E : real { a } enum F : E { a = E.a } - //typedef const F G; + alias G = const(F); static assert(is(OriginalType!E == real)); static assert(is(OriginalType!F == real)); - //static assert(is(OriginalType!G == const real)); + static assert(is(OriginalType!G == const real)); } /** @@ -6187,6 +6024,7 @@ template Largest(T...) if(T.length >= 1) } } +/// unittest { static assert(is(Largest!(uint, ubyte, ushort, real) == real)); @@ -6222,14 +6060,19 @@ template Signed(T) alias Signed = ModifyTypePreservingSTC!(Impl, OriginalType!T); } +/// unittest { alias S1 = Signed!uint; - alias S2 = Signed!(const(uint)); - alias S3 = Signed!(immutable(uint)); static assert(is(S1 == int)); + alias S2 = Signed!(const(uint)); static assert(is(S2 == const(int))); + alias S3 = Signed!(immutable(uint)); static assert(is(S3 == immutable(int))); +} + +unittest +{ static assert(is(Signed!float == float)); static if (is(__vector(int[4])) && is(__vector(uint[4]))) { @@ -6262,7 +6105,11 @@ unittest static assert(mostNegative!double == -double.max); static assert(mostNegative!real == -real.max); static assert(mostNegative!bool == false); +} +/// +unittest +{ foreach(T; TypeTuple!(bool, byte, short, int, long)) static assert(mostNegative!T == T.min); @@ -6357,11 +6204,6 @@ private string removeDummyEnvelope(string s) unittest { - //typedef int MyInt; - //MyInt test() { return 0; } - //static assert(mangledName!MyInt[$ - 7 .. $] == "T5MyInt"); // XXX depends on bug 4237 - //static assert(mangledName!test[$ - 7 .. $] == "T5MyInt"); - class C { int value() @property { return 0; } } static assert(mangledName!int == int.mangleof); static assert(mangledName!C == C.mangleof); diff --git a/std/typecons.d b/std/typecons.d index fe0da8c83c5..2d9f2d21518 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -43,8 +43,9 @@ Authors: $(WEB erdani.org, Andrei Alexandrescu), Kenji Hara */ module std.typecons; -import std.traits, std.range; -import std.typetuple : TypeTuple, allSatisfy; +import std.traits; +// FIXME +import std.typetuple; // : TypeTuple, allSatisfy; debug(Unique) import std.stdio; @@ -67,15 +68,15 @@ public: // Deferred in case we get some language support for checking uniqueness. version(None) /** - Allows safe construction of $(D Unique). It creates the resource and - guarantees unique ownership of it (unless $(D T) publishes aliases of + Allows safe construction of $(D Unique). It creates the resource and + guarantees unique ownership of it (unless $(D T) publishes aliases of $(D this)). Note: Nested structs/classes cannot be created. Params: args = Arguments to pass to $(D T)'s constructor. --- static class C {} - auto u = Unique!(C).create(); + auto u = Unique!(C).create(); --- */ static Unique!T create(A...)(auto ref A args) @@ -116,12 +117,12 @@ public: /** Constructor that takes a $(D Unique) of a type that is convertible to our type. - Typically used to transfer a $(D Unique) rvalue of derived type to + Typically used to transfer a $(D Unique) rvalue of derived type to a $(D Unique) of base type. Example: --- class C : Object {} - + Unique!C uc = new C; Unique!Object uo = uc.release; --- @@ -133,7 +134,7 @@ public: _p = u._p; u._p = null; } - + /// Transfer ownership from a $(D Unique) of a type that is convertible to our type. void opAssign(U)(Unique!U u) if (is(u.RefT:RefT)) @@ -144,7 +145,7 @@ public: _p = u._p; u._p = null; } - + ~this() { debug(Unique) writeln("Unique destructor of ", (_p is null)? null: _p); @@ -227,7 +228,7 @@ unittest assert(!u.isEmpty); destroy(u); assert(deleted == 1); - + Unique!C uc = new C; static assert(!__traits(compiles, {Unique!Object uo = uc;})); Unique!Object uo = new C; @@ -400,7 +401,7 @@ template Tuple(Specs...) string decl = ""; foreach (i, name; staticMap!(extractName, fieldSpecs)) { - import std.string : format; + import std.format : format; decl ~= format("alias _%s = Identity!(field[%s]);", i, i); if (name.length != 0) @@ -437,7 +438,10 @@ template Tuple(Specs...) { auto lhs = typeof(tup1.field[i]).init; auto rhs = typeof(tup2.field[i]).init; - auto result = mixin("lhs "~op~" rhs"); + static if (op == "=") + lhs = rhs; + else + auto result = mixin("lhs "~op~" rhs"); } })); @@ -660,46 +664,41 @@ template Tuple(Specs...) /** * Converts to string. */ - static if (allSatisfy!(isPrintable, Types)) - string toString() + void toString(DG)(scope DG sink) { enum header = typeof(this).stringof ~ "(", footer = ")", separator = ", "; - - Appender!string w; - w.put(header); - foreach (i, Unused; Types) + sink(header); + foreach (i, Type; Types) { static if (i > 0) { - w.put(separator); + sink(separator); } // TODO: Change this once toString() works for shared objects. - static if (is(Unused == class) && is(Unused == shared)) - formattedWrite(w, "%s", field[i].stringof); + static if (is(Type == class) && is(typeof(Type.init) == shared)) + { + sink(Type.stringof); + } else { import std.format : FormatSpec, formatElement; - - FormatSpec!char f; // "%s" - formatElement(w, field[i], f); + FormatSpec!char f; + formatElement(sink, field[i], f); } } - w.put(footer); - return w.data; + sink(footer); + } + + string toString()() + { + import std.conv : to; + return this.to!string; } } } -private enum bool isPrintable(T) = - is(typeof({ - import std.format : formattedWrite; - - Appender!string w; - formattedWrite(w, "%s", T.init); - })); - /** Return a copy of a Tuple with its fields in reverse order. */ @@ -755,6 +754,7 @@ private template ReverseTupleSpecs(T...) unittest { + import std.conv; { Tuple!(int, "a", int, "b") nosh; static assert(nosh.length == 2); @@ -779,16 +779,23 @@ unittest nosh[0] = 5; nosh[1] = 0; assert(nosh[0] == 5 && nosh[1] == 0); - assert(nosh.toString() == "Tuple!(int, real)(5, 0)", nosh.toString()); + assert(nosh.to!string == "Tuple!(int, real)(5, 0)", nosh.to!string); Tuple!(int, int) yessh; nosh = yessh; } + { + class A {} + Tuple!(int, shared A) nosh; + nosh[0] = 5; + assert(nosh[0] == 5 && nosh[1] is null); + assert(nosh.to!string == "Tuple!(int, shared(A))(5, shared(A))"); + } { Tuple!(int, string) t; t[0] = 10; t[1] = "str"; assert(t[0] == 10 && t[1] == "str"); - assert(t.toString() == `Tuple!(int, string)(10, "str")`, t.toString()); + assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string); } { Tuple!(int, "a", double, "b") x; @@ -863,7 +870,7 @@ unittest static struct R { Tuple!(int, int) _front; - @property ref Tuple!(int, int) front() { return _front; } + @property ref Tuple!(int, int) front() return { return _front; } @property bool empty() { return _front[0] >= 10; } void popFront() { ++_front[0]; } } @@ -1075,22 +1082,113 @@ unittest static assert(Fields.fieldNames == TypeTuple!("id", "", "")); } +// Bugzilla 13837 +unittest +{ + // New behaviour, named arguments. + static assert(is( + typeof(tuple!("x")(1)) == Tuple!(int, "x"))); + static assert(is( + typeof(tuple!("x")(1.0)) == Tuple!(double, "x"))); + static assert(is( + typeof(tuple!("x")("foo")) == Tuple!(string, "x"))); + static assert(is( + typeof(tuple!("x", "y")(1, 2.0)) == Tuple!(int, "x", double, "y"))); + + auto a = tuple!("a", "b", "c")("1", 2, 3.0f); + static assert(is(typeof(a.a) == string)); + static assert(is(typeof(a.b) == int)); + static assert(is(typeof(a.c) == float)); + + // Old behaviour, but with explicit type parameters. + static assert(is( + typeof(tuple!(int, double)(1, 2.0)) == Tuple!(int, double))); + static assert(is( + typeof(tuple!(const int)(1)) == Tuple!(const int))); + static assert(is( + typeof(tuple()) == Tuple!())); + + // Nonsensical behaviour + static assert(!__traits(compiles, tuple!(1)(2))); + static assert(!__traits(compiles, tuple!("x")(1, 2))); + static assert(!__traits(compiles, tuple!("x", "y")(1))); + static assert(!__traits(compiles, tuple!("x")())); + static assert(!__traits(compiles, tuple!("x", int)(2))); +} + +unittest +{ + class C {} + Tuple!(Rebindable!(const C)) a; + Tuple!(const C) b; + a = b; +} + +@nogc unittest +{ + alias T = Tuple!(string, "s"); + T x; + x = T.init; +} + /** Returns a $(D Tuple) object instantiated and initialized according to the arguments. - -Example: ----- -auto value = tuple(5, 6.7, "hello"); -assert(value[0] == 5); -assert(value[1] == 6.7); -assert(value[2] == "hello"); ----- */ -Tuple!T tuple(T...)(T args) +template tuple(Names...) { - return typeof(return)(args); + auto tuple(Args...)(Args args) + { + static if (Names.length == 0) + { + // No specified names, just infer types from Args... + return Tuple!Args(args); + } + else static if (!is(typeof(Names[0]) : string)) + { + // Names[0] isn't a string, must be explicit types. + return Tuple!Names(args); + } + else + { + // Names[0] is a string, so must be specifying names. + static assert(Names.length == Args.length, + "Insufficient number of names given."); + + // Interleave(a, b).and(c, d) == (a, c, b, d) + // This is to get the interleaving of types and names for Tuple + // e.g. Tuple!(int, "x", string, "y") + template Interleave(A...) + { + template and(B...) if (B.length == 1) + { + alias TypeTuple!(A[0], B[0]) and; + } + + template and(B...) if (B.length != 1) + { + alias TypeTuple!(A[0], B[0], + Interleave!(A[1..$]).and!(B[1..$])) and; + } + } + return Tuple!(Interleave!(Args).and!(Names))(args); + } + } +} + +/// +unittest +{ + auto value = tuple(5, 6.7, "hello"); + assert(value[0] == 5); + assert(value[1] == 6.7); + assert(value[2] == "hello"); + + // Field names can be provided. + auto entry = tuple!("index", "value")(4, "Hello"); + assert(entry.index == 4); + assert(entry.value == "Hello"); } /** @@ -1109,6 +1207,7 @@ template isTuple(T) } } +/// unittest { static assert(isTuple!(Tuple!())); @@ -1116,7 +1215,10 @@ unittest static assert(isTuple!(Tuple!(int, real, string))); static assert(isTuple!(Tuple!(int, "x", real, "y"))); static assert(isTuple!(Tuple!(int, Tuple!(real), string))); +} +unittest +{ static assert(isTuple!(const Tuple!(int))); static assert(isTuple!(immutable Tuple!(int))); @@ -1212,6 +1314,7 @@ template Rebindable(T) if (is(T == class) || is(T == interface) || isDynamicArra { static if (isDynamicArray!T) { + import std.range.primitives : ElementEncodingType; alias Rebindable = const(ElementEncodingType!T)[]; } else @@ -1252,7 +1355,7 @@ Rebindable!T rebindable(T)(Rebindable!T obj) unittest { - interface CI { const int foo(); } + interface CI { int foo() const; } class C : CI { int foo() const { return 42; } @property int bar() const { return 23; } @@ -1391,14 +1494,6 @@ unittest /** Order the provided members to minimize size while preserving alignment. Returns a declaration to be mixed in. - -Example: ---- -struct Banner { - mixin(alignForSize!(byte[6], double)(["name", "height"])); -} ---- - Alignment is not always optimal for 80-bit reals, nor for structs declared as align(1). */ @@ -1430,6 +1525,14 @@ string alignForSize(E...)(string[] names...) return s; } +/// +unittest +{ + struct Banner { + mixin(alignForSize!(byte[6], double)(["name", "height"])); + } +} + unittest { enum x = alignForSize!(int[], char[3], short, double[5])("x", "y","z", "w"); @@ -1453,15 +1556,6 @@ the absence of a value. If default constructed, a $(D Nullable!T) object starts in the null state. Assigning it renders it non-null. Calling $(D nullify) can nullify it again. -Example: ----- -Nullable!int a; -assert(a.isNull); -a = 5; -assert(!a.isNull); -assert(a == 5); ----- - Practically $(D Nullable!T) stores a $(D T) and a $(D bool). */ struct Nullable(T) @@ -1478,6 +1572,23 @@ Constructor initializing $(D this) with $(D value). _isNull = false; } + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(_value, fmt); + } + } + } + /** Returns $(D true) if and only if $(D this) is in the null state. */ @@ -1523,6 +1634,16 @@ $(D this) must not be in the null state. alias get this; } +/// +unittest +{ + Nullable!int a; + assert(a.isNull); + a = 5; + assert(!a.isNull); + assert(a == 5); +} + unittest { import std.exception : assertThrown; @@ -1757,6 +1878,43 @@ unittest import std.datetime; Nullable!SysTime time = SysTime(0); } +unittest +{ + import std.conv: to; + import std.array; + + // Bugzilla 10915 + Appender!string buffer; + + Nullable!int ni; + assert(ni.to!string() == "Nullable.null"); + + struct Test { string s; } + alias NullableTest = Nullable!Test; + + NullableTest nt = Test("test"); + assert(nt.to!string() == `Test("test")`); + + NullableTest ntn = Test("null"); + assert(ntn.to!string() == `Test("null")`); + + class TestToString + { + double d; + + this (double d) + { + this.d = d; + } + + override string toString() + { + return d.to!string(); + } + } + Nullable!TestToString ntts = new TestToString(2.5); + assert(ntts.to!string() == "2.5"); +} /** Just like $(D Nullable!T), except that the null state is defined as a @@ -1777,12 +1935,38 @@ Constructor initializing $(D this) with $(D value). _value = value; } + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(_value, fmt); + } + } + } + /** Returns $(D true) if and only if $(D this) is in the null state. */ @property bool isNull() const { - return _value == nullValue; + //Need to use 'is' if T is a nullable type and + //nullValue is null, or it's a compiler error + static if (is(CommonType!(T, typeof(null)) == T) && nullValue is null) + { + return _value is nullValue; + } + else + { + return _value == nullValue; + } } /** @@ -1911,6 +2095,43 @@ unittest c = a; } } +unittest +{ + import std.conv: to; + + // Bugzilla 10915 + Nullable!(int, 1) ni = 1; + assert(ni.to!string() == "Nullable.null"); + + struct Test { string s; } + alias NullableTest = Nullable!(Test, Test("null")); + + NullableTest nt = Test("test"); + assert(nt.to!string() == `Test("test")`); + + NullableTest ntn = Test("null"); + assert(ntn.to!string() == "Nullable.null"); + + class TestToString + { + double d; + + this(double d) + { + this.d = d; + } + + override string toString() + { + return d.to!string(); + } + } + alias NullableTestToString = Nullable!(TestToString, null); + + NullableTestToString ntts = new TestToString(2.5); + assert(ntts.to!string() == "2.5"); +} + /** Just like $(D Nullable!T), except that the object refers to a value @@ -1930,6 +2151,23 @@ Constructor binding $(D this) with $(D value). _value = value; } + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(*_value, fmt); + } + } + } + /** Binds the internal state to $(D value). */ @@ -2083,6 +2321,40 @@ unittest c = a; } } +unittest +{ + import std.conv: to; + + // Bugzilla 10915 + NullableRef!int nri; + assert(nri.to!string() == "Nullable.null"); + + struct Test + { + string s; + } + NullableRef!Test nt = new Test("test"); + assert(nt.to!string() == `Test("test")`); + + class TestToString + { + double d; + + this(double d) + { + this.d = d; + } + + override string toString() + { + return d.to!string(); + } + } + TestToString tts = new TestToString(2.5); + NullableRef!TestToString ntts = &tts; + assert(ntts.to!string() == "2.5"); +} + /** $(D BlackHole!Base) is a subclass of $(D Base) which automatically implements @@ -2130,7 +2402,7 @@ unittest { interface I_1 { real test(); } auto o = new BlackHole!I_1; - assert(o.test().isNaN); // NaN + assert(o.test().isNaN()); // NaN } // doc example { @@ -2265,7 +2537,7 @@ string generateLogger(C, alias fun)() @property string stmt; stmt ~= q{ struct Importer { import std.stdio; } }; - stmt ~= `Importer.writeln$(LPAREN)"Log: ` ~ qname ~ `(", args, ")"$(RPAREN);`; + stmt ~= `Importer.writeln("Log: ` ~ qname ~ `(", args, ")");`; static if (!__traits(isAbstractFunction, fun)) { static if (is(ReturnType!fun == void)) @@ -2327,7 +2599,7 @@ class AutoImplement(Base, alias how, alias what = isAbstractFunction) : Base /* * Code-generating stuffs are encupsulated in this helper template so that - * namespace pollusion, which can cause name confliction with Base's public + * namespace pollution, which can cause name confliction with Base's public * members, should be minimized. */ private template AutoImplement_Helper(string myName, string baseName, @@ -2338,30 +2610,15 @@ private static: // Internal stuffs //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// - // this would be deprecated by std.typelist.Filter - template staticFilter(alias pred, lst...) - { - static if (lst.length > 0) - { - alias tail = staticFilter!(pred, lst[1 .. $]); - // - static if (pred!(lst[0])) - alias staticFilter = TypeTuple!(lst[0], tail); - else - alias staticFilter = tail; - } - else - alias staticFilter = TypeTuple!(); - } - // Returns function overload sets in the class C, filtered with pred. template enumerateOverloads(C, alias pred) { template Impl(names...) { + import std.typetuple : Filter; static if (names.length > 0) { - alias methods = staticFilter!(pred, MemberFunctionsTuple!(C, names[0])); + alias methods = Filter!(pred, MemberFunctionsTuple!(C, names[0])); alias next = Impl!(names[1 .. $]); static if (methods.length > 0) @@ -2418,7 +2675,7 @@ private static: // overloaded function with the name. template INTERNAL_FUNCINFO_ID(string name, size_t i) { - import std.string : format; + import std.format : format; enum string INTERNAL_FUNCINFO_ID = format("F_%s_%s", name, i); } @@ -2697,7 +2954,7 @@ private static: //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// // Internal stuffs //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// - import std.string; + import std.format; enum CONSTRUCTOR_NAME = "__ctor"; @@ -2785,7 +3042,7 @@ private static: public string generateFunction( string myFuncInfo, string name, func... )() @property { - import std.string : format; + import std.format : format; enum isCtor = (name == CONSTRUCTOR_NAME); @@ -3770,20 +4027,6 @@ autoInit == RefCountedAutoInitialize.no), user code must call either $(D refCountedStore.isInitialized) or $(D refCountedStore.ensureInitialized) before attempting to access the payload. Not doing so results in null pointer dereference. - -Example: ----- -// A pair of an $(D int) and a $(D size_t) - the latter being the -// reference count - will be dynamically allocated -auto rc1 = RefCounted!int(5); -assert(rc1 == 5); -// No more allocation, add just one extra reference count -auto rc2 = rc1; -// Reference semantics -rc2 = 42; -assert(rc1 == 42); -// the pair will be freed when rc1 and rc2 go out of scope ----- */ struct RefCounted(T, RefCountedAutoInitialize autoInit = RefCountedAutoInitialize.yes) @@ -3977,6 +4220,21 @@ assert(refCountedStore.isInitialized)). alias refCountedPayload this; } +/// +unittest +{ + // A pair of an $(D int) and a $(D size_t) - the latter being the + // reference count - will be dynamically allocated + auto rc1 = RefCounted!int(5); + assert(rc1 == 5); + // No more allocation, add just one extra reference count + auto rc2 = rc1; + // Reference semantics + rc2 = 42; + assert(rc1 == 42); + // the pair will be freed when rc1 and rc2 go out of scope +} + unittest { RefCounted!int* p; @@ -4062,54 +4320,93 @@ unittest /** Make proxy for $(D a). - -Example: ----- -struct MyInt + */ +mixin template Proxy(alias a) { - private int value; - mixin Proxy!value; - - this(int n){ value = n; } -} + static if (is(typeof(this) == class)) + { + override bool opEquals(Object o) + { + if (auto b = cast(typeof(this))o) + { + import std.algorithm : startsWith; + static assert(startsWith(a.stringof, "this.")); + return a == mixin("b."~a.stringof[5..$]); // remove "this." + } + return false; + } -MyInt n = 10; + bool opEquals(T)(T b) + if (is(typeof(a):T) || is(typeof(a.opEquals(b))) || is(typeof(b.opEquals(a)))) + { + static if (is(typeof(a.opEquals(b)))) + return a.opEquals(b); + else static if (is(typeof(b.opEquals(a)))) + return b.opEquals(a); + else + return a == b; + } -// Enable operations that original type has. -++n; -assert(n == 11); -assert(n * 2 == 22); + override int opCmp(Object o) + { + if (auto b = cast(typeof(this))o) + { + import std.algorithm : startsWith; + static assert(startsWith(a.stringof, "this.")); // remove "this." + return a < mixin("b."~a.stringof[5..$]) ? -1 + : a > mixin("b."~a.stringof[5..$]) ? +1 : 0; + } + static if (is(typeof(a) == class)) + return a.opCmp(o); + else + throw new Exception("Attempt to compare a "~typeid(this).toString~" and a "~typeid(o).toString); + } -void func(int n) { } + int opCmp(T)(auto ref const T b) + if (is(typeof(a):T) || is(typeof(a.opCmp(b))) || is(typeof(b.opCmp(a)))) + { + static if (is(typeof(a.opCmp(b)))) + return a.opCmp(b); + else static if (is(typeof(b.opCmp(a)))) + return -b.opCmp(b); + else + return a < b ? -1 : a > b ? +1 : 0; + } -// Disable implicit conversions to original type. -//int x = n; -//func(n); ----- - */ -mixin template Proxy(alias a) -{ - auto ref opEquals(this X, B)(auto ref B b) - { - static if (is(immutable B == immutable typeof(this))) + override hash_t toHash() const nothrow @trusted { - import std.algorithm; - static assert(startsWith(a.stringof, "this.")); - return a == mixin("b."~a.stringof[5..$]); // remove "this." + return typeid(typeof(a)).getHash(cast(const void*)&a); } - else - return a == b; } - - auto ref opCmp(this X, B)(auto ref B b) - if (!is(typeof(a.opCmp(b))) || !is(typeof(b.opCmp(a)))) + else { - static if (is(typeof(a.opCmp(b)))) - return a.opCmp(b); - else static if (is(typeof(b.opCmp(a)))) - return -b.opCmp(a); - else - return a < b ? -1 : a > b ? +1 : 0; + auto ref opEquals(this X, B)(auto ref B b) + { + static if (is(immutable B == immutable typeof(this))) + { + import std.algorithm : startsWith; + static assert(startsWith(a.stringof, "this.")); + return a == mixin("b."~a.stringof[5..$]); // remove "this." + } + else + return a == b; + } + + auto ref opCmp(this X, B)(auto ref B b) + if (!is(typeof(a.opCmp(b))) || !is(typeof(b.opCmp(a)))) + { + static if (is(typeof(a.opCmp(b)))) + return a.opCmp(b); + else static if (is(typeof(b.opCmp(a)))) + return -b.opCmp(a); + else + return a < b ? -1 : a > b ? +1 : 0; + } + + hash_t toHash() const nothrow @trusted + { + return typeid(typeof(a)).getHash(cast(const void*)&a); + } } auto ref opCall(this X, Args...)(auto ref Args args) { return a(args); } @@ -4207,6 +4504,32 @@ mixin template Proxy(alias a) alias opDollar = a.opDollar; } } + +/// +unittest +{ + struct MyInt + { + private int value; + mixin Proxy!value; + + this(int n){ value = n; } + } + + MyInt n = 10; + + // Enable operations that original type has. + ++n; + assert(n == 11); + assert(n * 2 == 22); + + void func(int n) { } + + // Disable implicit conversions to original type. + //int x = n; + //func(n); +} + unittest { static struct MyInt @@ -4300,13 +4623,13 @@ unittest { int field; - @property const int val1(){ return field; } - @property void val1(int n){ field = n; } + @property int val1() const { return field; } + @property void val1(int n) { field = n; } - @property ref int val2(){ return field; } + @property ref int val2() { return field; } - const int func(int x, int y){ return x; } - void func1(ref int a){ a = 9; } + int func(int x, int y) const { return x; } + void func1(ref int a) { a = 9; } T ifti1(T)(T t) { return t; } void ifti2(Args...)(Args args) { } @@ -4369,6 +4692,120 @@ unittest // template member function assert(h.tempfunc!int() == 0); } + +unittest // about Proxy inside a class +{ + class MyClass + { + int payload; + mixin Proxy!payload; + this(int i){ payload = i; } + string opCall(string msg){ return msg; } + int pow(int i){ return payload ^^ i; } + } + + class MyClass2 + { + MyClass payload; + mixin Proxy!payload; + this(int i){ payload = new MyClass(i); } + } + + class MyClass3 + { + int payload; + mixin Proxy!payload; + this(int i){ payload = i; } + } + + // opEquals + Object a = new MyClass(5); + Object b = new MyClass(5); + Object c = new MyClass2(5); + Object d = new MyClass3(5); + assert(a == b); + assert((cast(MyClass)a) == 5); + assert(5 == (cast(MyClass)b)); + assert(5 == cast(MyClass2)c); + assert(a != d); + + assert(c != a); + // oops! above line is unexpected, isn't it? + // the reason is below. + // MyClass2.opEquals knows MyClass but, + // MyClass.opEquals doesn't know MyClass2. + // so, c.opEquals(a) is true, but a.opEquals(c) is false. + // furthermore, opEquals(T) couldn't be invoked. + assert((cast(MyClass2)c) != (cast(MyClass)a)); + + // opCmp + Object e = new MyClass2(7); + assert(a < cast(MyClass2)e); // OK. and + assert(e > a); // OK, but... + // assert(a < e); // RUNTIME ERROR! + // assert((cast(MyClass)a) < e); // RUNTIME ERROR! + assert(3 < cast(MyClass)a); + assert((cast(MyClass2)e) < 11); + + // opCall + assert((cast(MyClass2)e)("hello") == "hello"); + + // opCast + assert((cast(MyClass)(cast(MyClass2)c)) == a); + assert((cast(int)(cast(MyClass2)c)) == 5); + + // opIndex + class MyClass4 + { + string payload; + mixin Proxy!payload; + this(string s){ payload = s; } + } + class MyClass5 + { + MyClass4 payload; + mixin Proxy!payload; + this(string s){ payload = new MyClass4(s); } + } + auto f = new MyClass4("hello"); + assert(f[1] == 'e'); + auto g = new MyClass5("hello"); + assert(f[1] == 'e'); + + // opSlice + assert(f[2..4] == "ll"); + + // opUnary + assert(-(cast(MyClass2)c) == -5); + + // opBinary + assert((cast(MyClass)a) + (cast(MyClass2)c) == 10); + assert(5 + cast(MyClass)a == 10); + + // opAssign + (cast(MyClass2)c) = 11; + assert((cast(MyClass2)c) == 11); + (cast(MyClass2)c) = new MyClass(13); + assert((cast(MyClass2)c) == 13); + + // opOpAssign + assert((cast(MyClass2)c) += 4); + assert((cast(MyClass2)c) == 17); + + // opDispatch + assert((cast(MyClass2)c).pow(2) == 289); + + // opDollar + assert(f[2..$-1] == "ll"); + + // toHash + int[Object] hash; + hash[a] = 19; + hash[c] = 21; + assert(hash[b] == 19); + assert(hash[c] == 21); +} + unittest { struct MyInt @@ -4461,11 +4898,11 @@ alias TypeInt2 = Typedef!int; // The two Typedefs are the same type. static assert(is(TypeInt1 == TypeInt2)); -alias TypeFloat1 = Typedef!(float, float.init, "a"); -alias TypeFloat2 = Typedef!(float, float.init, "b"); +alias MoneyEuros = Typedef!(float, float.init, "euros"); +alias MoneyDollars = Typedef!(float, float.init, "dollars"); // The two Typedefs are _not_ the same type. -static assert(!is(TypeFloat1 == TypeFloat2)); +static assert(!is(MoneyEuros == MoneyDollars)); ---- Note: If a library routine cannot handle the Typedef type, @@ -4486,6 +4923,18 @@ struct Typedef(T, T init = T.init, string cookie=null) this(tdef.Typedef_payload); } + // We need to add special overload for cast(Typedef!X)exp, + // thus we can't simply inherit Proxy!Typedef_payload + T2 opCast(T2 : Typedef!(T, Unused), this X, T, Unused...)() + { + return T2(cast(T)Typedef_payload); + } + + auto ref opCast(T2, this X)() + { + return cast(T2)Typedef_payload; + } + mixin Proxy!Typedef_payload; } @@ -4616,6 +5065,76 @@ unittest // Issue 12596 assert(x == y); } +unittest // about toHash +{ + import std.typecons; + { + alias TD = Typedef!int; + int[TD] td; + td[TD(1)] = 1; + assert(td[TD(1)] == 1); + } + + { + alias TD = Typedef!(int[]); + int[TD] td; + td[TD([1,2,3,4])] = 2; + assert(td[TD([1,2,3,4])] == 2); + } + + { + alias TD = Typedef!(int[][]); + int[TD] td; + td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] = 3; + assert(td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] == 3); + } + + { + struct MyStruct{ int x; } + alias TD = Typedef!MyStruct; + int[TD] td; + td[TD(MyStruct(10))] = 4; + assert(TD(MyStruct(20)) !in td); + assert(td[TD(MyStruct(10))] == 4); + } + + { + static struct MyStruct2 + { + int x; + size_t toHash() const nothrow @safe { return x; } + bool opEquals(ref const MyStruct2 r) const { return r.x == x; } + } + + alias TD = Typedef!MyStruct2; + int[TD] td; + td[TD(MyStruct2(50))] = 5; + assert(td[TD(MyStruct2(50))] == 5); + } + + { + class MyClass{} + alias TD = Typedef!MyClass; + int[TD] td; + auto c = new MyClass; + td[TD(c)] = 6; + assert(TD(new MyClass) !in td); + assert(td[TD(c)] == 6); + } +} + +unittest +{ + alias String = Typedef!(char[]); + alias CString = Typedef!(const(char)[]); + CString cs = "fubar"; + String s = cast(String)cs; + assert(cs == s); + char[] s2 = cast(char[])cs; + const(char)[] cs2 = cast(const(char)[])s; + assert(s2 == cs2); +} + /** Allocates a $(D class) object right inside the current scope, therefore avoiding the overhead of $(D new). This facility is unsafe; @@ -4977,8 +5496,11 @@ bool) makes the flag's meaning visible in calls. Each yes/no flag has its own type, which makes confusions and mix-ups impossible. Example: + +Code calling $(D getLine) (usually far away from its definition) can't be +understood without looking at the documentation, even by users familiar with +the API: ---- -// Before string getLine(bool keepTerminator) { ... @@ -4986,24 +5508,25 @@ string getLine(bool keepTerminator) ... } ... -// Code calling getLine (usually far away from its definition) can't -// be understood without looking at the documentation, even by users -// familiar with the API. Assuming the reverse meaning -// (i.e. "ignoreTerminator") and inserting the wrong code compiles and -// runs with erroneous results. auto line = getLine(false); +---- + +Assuming the reverse meaning (i.e. "ignoreTerminator") and inserting the wrong +code compiles and runs with erroneous results. -// After -string getLine(Flag!"KeepTerminator" keepTerminator) +After replacing the boolean parameter with an instantiation of $(D Flag), code +calling $(D getLine) can be easily read and understood even by people not +fluent with the API: + +---- +string getLine(Flag!"keepTerminator" keepTerminator) { ... if (keepTerminator) ... ... } ... -// Code calling getLine can be easily read and understood even by -// people not fluent with the API. -auto line = getLine(Flag!"KeepTerminator".yes); +auto line = getLine(Flag!"keepTerminator".yes); ---- Passing categorical data by means of unstructured $(D bool) @@ -5013,9 +5536,19 @@ kinds of coupling. The author argues citing several studies that coupling has a negative effect on code quality. $(D Flag) offers a simple structuring method for passing yes/no flags to APIs. -As a perk, the flag's name may be any string and as such can include -characters not normally allowed in identifiers, such as -spaces and dashes. +An alias can be used to reduce the verbosity of the flag's type: +---- +alias KeepTerminator = Flag!"keepTerminator"; +string getline(KeepTerminator keepTerminator) +{ + ... + if (keepTerminator) ... + ... +} +... +// Code calling getLine can now refer to flag values using the shorter name: +auto line = getLine(KeepTerminator.yes); +---- */ template Flag(string name) { /// @@ -5059,8 +5592,8 @@ struct No enum opDispatch = Flag!name.no; } } -//template no(string name) { enum Flag!name no = Flag!name.no; } +/// unittest { Flag!"abc" flag1; @@ -5075,3 +5608,296 @@ unittest assert(flag1 == Yes.abc); } +/** +Detect whether an enum is of integral type and has only "flag" values +(i.e. values with a bit count of exactly 1). +Additionally, a zero value is allowed for compatibility with enums including +a "None" value. +*/ +template isBitFlagEnum(E) +{ + static if (is(E Base == enum) && isIntegral!Base) + { + enum isBitFlagEnum = (E.min >= 0) && + { + foreach (immutable flag; EnumMembers!E) + { + Base value = flag; + value &= value - 1; + if (value != 0) return false; + } + return true; + }(); + } + else + { + enum isBitFlagEnum = false; + } +} + +/// +@safe pure nothrow unittest +{ + enum A + { + None, + A = 1<<0, + B = 1<<1, + C = 1<<2, + D = 1<<3, + } + + static assert(isBitFlagEnum!A); + + enum B + { + A, + B, + C, + D // D == 3 + } + + static assert(!isBitFlagEnum!B); + + enum C: double + { + A = 1<<0, + B = 1<<1 + } + + static assert(!isBitFlagEnum!C); +} + +/** +A typesafe structure for storing combination of enum values. + +This template defines a simple struct to represent bitwise OR combinations of +enum values. It can be used if all the enum values are integral constants with +a bit count of at most 1, or if the $(D unsafe) parameter is explicitly set to +Yes. +This is much safer than using the enum itself to store +the OR combination, which can produce surprising effects like this: +---- +enum E +{ + A = 1<<0, + B = 1<<1 +} +E e = E.A | E.B; +// will throw SwitchError +final switch(e) +{ + case E.A: + return; + case E.B: + return; +} +---- +*/ +struct BitFlags(E, Flag!"unsafe" unsafe = No.unsafe) if (unsafe || isBitFlagEnum!(E)) +{ +@safe @nogc pure nothrow: +private: + enum isBaseEnumType(T) = is(E == T); + alias Base = OriginalType!E; + Base mValue; + static struct Negation + { + @safe @nogc pure nothrow: + private: + Base mValue; + + // Prevent non-copy construction outside the module. + @disable this(); + this(Base value) + { + mValue = value; + } + } + +public: + this(E flag) + { + this = flag; + } + + this(T...)(T flags) + if (allSatisfy!(isBaseEnumType, T)) + { + this = flags; + } + + bool opCast(B: bool)() const + { + return mValue != 0; + } + + Base opCast(B)() const + if (isImplicitlyConvertible!(Base, B)) + { + return mValue; + } + + Negation opUnary(string op)() const + if (op == "~") + { + return Negation(~mValue); + } + + auto ref opAssign(T...)(T flags) + if (allSatisfy!(isBaseEnumType, T)) + { + mValue = 0; + foreach (E flag; flags) + { + mValue |= flag; + } + return this; + } + + auto ref opAssign(E flag) + { + mValue = flag; + return this; + } + + auto ref opOpAssign(string op: "|")(BitFlags flags) + { + mValue |= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "&")(BitFlags flags) + { + mValue &= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "|")(E flag) + { + mValue |= flag; + return this; + } + + auto ref opOpAssign(string op: "&")(E flag) + { + mValue &= flag; + return this; + } + + auto ref opOpAssign(string op: "&")(Negation negatedFlags) + { + mValue &= negatedFlags.mValue; + return this; + } + + auto opBinary(string op)(BitFlags flags) const + if (op == "|" || op == "&") + { + BitFlags result = this; + result.opOpAssign!op(flags); + return result; + } + + auto opBinary(string op)(E flag) const + if (op == "|" || op == "&") + { + BitFlags result = this; + result.opOpAssign!op(flag); + return result; + } + + auto opBinary(string op: "&")(Negation negatedFlags) const + { + BitFlags result = this; + result.opOpAssign!op(negatedFlags); + return result; + } + + auto opBinaryRight(string op)(E flag) const + if (op == "|" || op == "&") + { + return opBinary!op(flag); + } +} + +/// BitFlags can be manipulated with the usual operators +@safe @nogc pure nothrow unittest +{ + // You can use such an enum with BitFlags straight away + enum Enum + { + None, + A = 1<<0, + B = 1<<1, + C = 1<<2 + } + static assert(__traits(compiles, BitFlags!Enum)); + + // You need to specify the $(D unsafe) parameter for enum with custom values + enum UnsafeEnum + { + A, + B, + C, + D = B|C + } + static assert(!__traits(compiles, BitFlags!UnsafeEnum)); + static assert(__traits(compiles, BitFlags!(UnsafeEnum, Yes.unsafe))); + + immutable BitFlags!Enum flags_empty; + // A default constructed BitFlags has no value set + assert(!(flags_empty & Enum.A) && !(flags_empty & Enum.B) && !(flags_empty & Enum.C)); + + // Value can be set with the | operator + immutable BitFlags!Enum flags_A = flags_empty | Enum.A; + + // And tested with the & operator + assert(flags_A & Enum.A); + + // Which commutes + assert(Enum.A & flags_A); + + // BitFlags can be variadically initialized + immutable BitFlags!Enum flags_AB = BitFlags!Enum(Enum.A, Enum.B); + assert((flags_AB & Enum.A) && (flags_AB & Enum.B) && !(flags_AB & Enum.C)); + + // Use the ~ operator for subtracting flags + immutable BitFlags!Enum flags_B = flags_AB & ~BitFlags!Enum(Enum.A); + assert(!(flags_B & Enum.A) && (flags_B & Enum.B) && !(flags_B & Enum.C)); + + // You can use the EnumMembers template to set all flags + immutable BitFlags!Enum flags_all = EnumMembers!Enum; + + // use & between BitFlags for intersection + immutable BitFlags!Enum flags_BC = BitFlags!Enum(Enum.B, Enum.C); + assert (flags_B == (flags_BC & flags_AB)); + + // All the binary operators work in their assignment version + BitFlags!Enum temp = flags_empty; + temp |= flags_AB; + assert(temp == (flags_empty | flags_AB)); + temp = flags_empty; + temp |= Enum.B; + assert(temp == (flags_empty | Enum.B)); + temp = flags_empty; + temp &= flags_AB; + assert(temp == (flags_empty & flags_AB)); + temp = flags_empty; + temp &= Enum.A; + assert(temp == (flags_empty & Enum.A)); + + // BitFlags with no value set evaluate to false + assert(!flags_empty); + + // BitFlags with at least one value set evaluate to true + assert(flags_A); + + // This can be useful to check intersection between BitFlags + assert(flags_A & flags_AB); + assert(flags_AB & Enum.A); + + // Finally, you can of course get you raw value out of flags + auto value = cast(int)flags_A; + assert(value == Enum.A); +} diff --git a/std/typelist.d b/std/typelist.d index ac5708df968..9609b821bf3 100644 --- a/std/typelist.d +++ b/std/typelist.d @@ -31,7 +31,7 @@ * ---- * * Copyright: Copyright Bartosz Milewski 2008- 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB bartoszmilewski.wordpress.com, Bartosz Milewski) * Source: $(PHOBOSSRC std/_typelist.d) */ @@ -40,6 +40,7 @@ * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) */ +deprecated("Please use std.typecons instead. This module will be removed in March 2015.") module std.typelist; version(unittest) { import std.typetuple; diff --git a/std/typetuple.d b/std/typetuple.d index a6a5523db48..bba40bb81c2 100644 --- a/std/typetuple.d +++ b/std/typetuple.d @@ -21,7 +21,7 @@ * WIKI = Phobos/StdTypeTuple * * Copyright: Copyright Digital Mars 2005 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: * $(WEB digitalmars.com, Walter Bright), * $(WEB klickverbot.at, David Nadlinger) @@ -617,7 +617,7 @@ unittest } /** -Tests whether all given items satisfy a template predicate, i.e. evaluates to +Tests whether any given items satisfy a template predicate, i.e. evaluates to $(D F!(T[0]) || F!(T[1]) || ... || F!(T[$ - 1])). Evaluation is $(I not) short-circuited if a true result is encountered; the diff --git a/std/uni.d b/std/uni.d index a283489ce71..84030d5feb1 100644 --- a/std/uni.d +++ b/std/uni.d @@ -1,8 +1,6 @@ // Written in the D programming language. /++ - $(SECTION Overview) - $(P The $(D std.uni) module provides an implementation of fundamental Unicode algorithms and data structures. This doesn't include UTF encoding and decoding primitives, @@ -157,7 +155,7 @@ $(P The following is a list of important Unicode notions and definitions. Any conventions used specifically in this module alone are marked as such. The descriptions are based on the formal - definition as found in $(WEB http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf, + definition as found in $(WEB www.unicode.org/versions/Unicode6.2.0/ch03.pdf, chapter three of The Unicode Standard Core Specification.) ) @@ -373,7 +371,7 @@ $(P The recommended solution (see Unicode Implementation Guidelines) is using multi-stage tables that are an implementation of the - $(WEB http://en.wikipedia.org/wiki/Trie, Trie) data structure with integer + $(WEB en.wikipedia.org/wiki/Trie, Trie) data structure with integer keys and a fixed number of stages. For the remainder of the section this will be called a fixed trie. The following describes a particular implementation that is aimed for the speed of access at the expense @@ -650,13 +648,10 @@ CLUSTER = $(S_LINK Grapheme cluster, grapheme cluster) +/ module std.uni; -static import std.ascii; -import std.traits, std.range, std.algorithm, std.conv, - std.typetuple, std.exception, core.stdc.stdlib; -import std.array; //@@BUG UFCS doesn't work with 'local' imports -import core.bitop; +import core.stdc.stdlib; +import std.traits, std.typetuple; +import std.range.primitives; -import std.typecons; // debug = std_uni; @@ -698,6 +693,7 @@ public enum dchar paraSep = '\u2029'; /// Constant $(CODEPOINT) (0x2029) - parag // test the intro example unittest { + import std.algorithm : find; // initialize code point sets using script/block or property name // set contains code points from both scripts. auto set = unicode("Cyrillic") | unicode("Armenian"); @@ -775,37 +771,8 @@ auto force(T, F)(F from) return from; } -// cheap algorithm grease ;) -auto adaptIntRange(T, F)(F[] src) -{ - //@@@BUG when in the 9 hells will map be copyable again?! - static struct ConvertIntegers - { - private F[] data; - - @property T front() - { - return force!T(data.front); - } - - void popFront(){ data.popFront(); } - - @property bool empty()const { return data.empty; } - - @property size_t length()const { return data.length; } - - auto opSlice(size_t s, size_t e) - { - return ConvertIntegers(data[s..e]); - } - - @property size_t opDollar(){ return data.length; } - } - return ConvertIntegers(src); -} - // repeat X times the bit-pattern in val assuming it's length is 'bits' -size_t replicateBits(size_t times, size_t bits)(size_t val) +size_t replicateBits(size_t times, size_t bits)(size_t val) @safe pure nothrow @nogc { static if(times == 1) return val; @@ -822,22 +789,25 @@ size_t replicateBits(size_t times, size_t bits)(size_t val) return replicateBits!(times/2, bits*2)((val< 8*size_t.sizeof) @@ -1528,12 +1501,15 @@ private auto packedArrayView(T)(inout(size_t)* ptr, size_t items) @trusted pure // Partially unrolled binary search using Shar's method //============================================================================ -private import std.math : pow; - string genUnrolledSwitchSearch(size_t size) { + import std.conv : to; + import core.bitop : bsr; + import std.array : replace; assert(isPowerOf2(size)); - string code = `auto power = bsr(m)+1; + string code = ` + import core.bitop : bsr; + auto power = bsr(m)+1; switch(power){`; size_t i = bsr(size); foreach_reverse(val; 0..bsr(size)) @@ -1598,14 +1574,16 @@ size_t switchUniformLowerBound(alias pred, Range, T)(Range range, T needle) } // -size_t floorPowerOf2(size_t arg) @safe pure nothrow +size_t floorPowerOf2(size_t arg) @safe pure nothrow @nogc { + import core.bitop : bsr; assert(arg > 1); // else bsr is undefined return 1< 1); // else bsr is undefined return 1< delta) @@ -1761,12 +1741,14 @@ unittest static T[] alloc(T)(size_t size) { + import std.exception : enforce; auto ptr = cast(T*)enforce(malloc(T.sizeof*size), "out of memory on C heap"); return ptr[0..size]; } static T[] realloc(T)(T[] arr, size_t size) { + import std.exception : enforce; if(!size) { destroy(arr); @@ -1886,6 +1868,9 @@ public struct CodepointInterval pure: uint[2] _tuple; alias _tuple this; + +@safe pure nothrow @nogc: + this(uint low, uint high) { _tuple[0] = low; @@ -1960,7 +1945,7 @@ pure: $(P Memory usage is 8 bytes per each contiguous interval in a set. The value semantics are achieved by using the - $(WEB http://en.wikipedia.org/wiki/Copy-on-write, COW) technique + $(WEB en.wikipedia.org/wiki/Copy-on-write, COW) technique and thus it's $(RED not) safe to cast this type to $(D_KEYWORD shared). ) @@ -1975,6 +1960,8 @@ pure: */ @trusted public struct InversionList(SP=GcPolicy) { + import std.range : assumeSorted; + public: /** @@ -2011,6 +1998,8 @@ public: //helper function that avoids sanity check to be CTFE-friendly private static fromIntervals(Range)(Range intervals) pure { + import std.algorithm : map; + import std.range : roundRobin; auto flattened = roundRobin(intervals.save.map!"a[0]"(), intervals.save.map!"a[1]"()); InversionList set; @@ -2021,6 +2010,7 @@ public: private static fromIntervals()(uint[] intervals...) pure in { + import std.conv : text; assert(intervals.length % 2 == 0, "Odd number of interval bounds [a, b)!"); for (uint i = 0; i < intervals.length; i += 2) { @@ -2055,6 +2045,7 @@ public: this()(uint[] intervals...) in { + import std.conv : text; assert(intervals.length % 2 == 0, "Odd number of interval bounds [a, b)!"); for (uint i = 0; i < intervals.length; i += 2) { @@ -2248,7 +2239,11 @@ public: - /// Obtains a set that is the inversion of this set. See also $(LREF inverted). + /** + * Obtains a set that is the inversion of this set. + * + * See_Also: $(LREF inverted) + */ auto opUnary(string op: "!")() { return this.inverted; @@ -2357,7 +2352,7 @@ public: unittest { import std.conv : to; - import std.string : format; + import std.format : format; import std.uni : unicode; assert(unicode.Cyrillic.to!string == @@ -2375,7 +2370,8 @@ public: unittest { - import std.string : format; + import std.exception : assertThrown; + import std.format : format; assertThrown!FormatException(format("%a", unicode.ASCII)); } @@ -2543,7 +2539,9 @@ public: */ string toSourceCode(string funcName="") { - import std.string; + import std.array : array; + import std.format : format; + import std.algorithm : countUntil; enum maxBinary = 3; static string linearScope(R)(R ivals, string indent) { @@ -2741,6 +2739,7 @@ private: // to make sure invariants hold void sanitize() { + import std.algorithm : sort, SwapStrategy, max; if (data.length == 0) return; alias Ival = CodepointInterval; @@ -2803,6 +2802,7 @@ private: } body { + import std.range : assumeSorted, SearchPolicy; auto range = assumeSorted(data[]); size_t pos; size_t a_idx = hint + range[hint..$].lowerBound!(SearchPolicy.gallop)(a).length; @@ -2983,6 +2983,7 @@ private: { // test examples import std.algorithm, std.typecons; + import std.range : iota; auto set = CodepointSet('A', 'D'+1, 'a', 'd'+1); set.byInterval.equalS([tuple('A', 'E'), tuple('a', 'e')]); set = unicode.ASCII; @@ -3042,7 +3043,7 @@ private: } // pedantic version for ctfe, and aligned-access only architectures -@trusted uint safeRead24(const ubyte* ptr, size_t idx) pure nothrow +@trusted uint safeRead24(const ubyte* ptr, size_t idx) pure nothrow @nogc { idx *= 3; version(LittleEndian) @@ -3054,7 +3055,7 @@ private: } // ditto -@trusted void safeWrite24(ubyte* ptr, uint val, size_t idx) pure nothrow +@trusted void safeWrite24(ubyte* ptr, uint val, size_t idx) pure nothrow @nogc { idx *= 3; version(LittleEndian) @@ -3072,7 +3073,7 @@ private: } // unaligned x86-like read/write functions -@trusted uint unalignedRead24(const ubyte* ptr, size_t idx) pure nothrow +@trusted uint unalignedRead24(const ubyte* ptr, size_t idx) pure nothrow @nogc { uint* src = cast(uint*)(ptr+3*idx); version(LittleEndian) @@ -3082,7 +3083,7 @@ private: } // ditto -@trusted void unalignedWrite24(ubyte* ptr, uint val, size_t idx) pure nothrow +@trusted void unalignedWrite24(ubyte* ptr, uint val, size_t idx) pure nothrow @nogc { uint* dest = cast(uint*)(cast(ubyte*)ptr + 3*idx); version(LittleEndian) @@ -3091,7 +3092,7 @@ private: *dest = (val<<8) | (*dest & 0xFF); } -uint read24(const ubyte* ptr, size_t idx) pure nothrow +uint read24(const ubyte* ptr, size_t idx) pure nothrow @nogc { static if(hasUnalignedReads) return __ctfe ? safeRead24(ptr, idx) : unalignedRead24(ptr, idx); @@ -3099,7 +3100,7 @@ uint read24(const ubyte* ptr, size_t idx) pure nothrow return safeRead24(ptr, idx); } -void write24(ubyte* ptr, uint val, size_t idx) pure nothrow +void write24(ubyte* ptr, uint val, size_t idx) pure nothrow @nogc { static if(hasUnalignedReads) return __ctfe ? safeWrite24(ptr, val, idx) : unalignedWrite24(ptr, val, idx); @@ -3121,6 +3122,7 @@ void write24(ubyte* ptr, uint val, size_t idx) pure nothrow this(Range)(Range range) if(isInputRange!Range && hasLength!Range) { + import std.algorithm : copy; length = range.length; copy(range, data[0..$-1]); } @@ -3128,6 +3130,7 @@ void write24(ubyte* ptr, uint val, size_t idx) pure nothrow this(Range)(Range range) if(isForwardRange!Range && !hasLength!Range) { + import std.algorithm : copy; auto len = walkLength(range.save); length = len; copy(range, data[0..$-1]); @@ -3165,6 +3168,7 @@ void write24(ubyte* ptr, uint val, size_t idx) pure nothrow //+ an extra slot for ref-count @property void length(size_t len) { + import std.algorithm : min, copy; if(len == 0) { if(!empty) @@ -3297,6 +3301,7 @@ private: } body { + import std.algorithm : copy; // dec shared ref-count refCount = count - 1; // copy to the new chunk of RAM @@ -3312,6 +3317,9 @@ private: @trusted unittest// Uint24 tests //@@@BUG@@ iota is system ?! { + import std.algorithm; + import std.conv; + import std.range; void funcRef(T)(ref T u24) { u24.length = 2; @@ -3399,6 +3407,7 @@ version(unittest) @trusted unittest// core set primitives test { + import std.conv; foreach(CodeList; AllSets) { CodeList a; @@ -3476,6 +3485,7 @@ version(unittest) @system unittest //@@@BUG@@@ iota is @system { import std.conv, std.range, std.algorithm; + import std.typecons; //ensure constructor handles bad ordering and overlap auto c1 = CodepointSet('а', 'я'+1, 'А','Я'+1); foreach(ch; chain(iota('а', 'я'+1), iota('А','Я'+1))) @@ -3516,6 +3526,7 @@ version(unittest) @trusted unittest { // full set operations + import std.conv; foreach(CodeList; AllSets) { CodeList a, b, c, d; @@ -3620,6 +3631,7 @@ version(unittest) @system: unittest// vs single dchar { + import std.conv; CodepointSet a = CodepointSet(10, 100, 120, 200); assert(a - 'A' == CodepointSet(10, 65, 66, 100, 120, 200), text(a - 'A')); assert((a & 'B') == CodepointSet(66, 67)); @@ -3627,6 +3639,7 @@ unittest// vs single dchar unittest// iteration & opIndex { + import std.conv; import std.typecons; foreach(CodeList; TypeTuple!(InversionList!(ReallocPolicy))) { @@ -3668,6 +3681,7 @@ unittest// iteration & opIndex // debug helper to get a shortened array dump auto arrayRepr(T)(T x) { + import std.conv : text; if(x.length > 32) { return text(x[0..16],"~...~", x[x.length-16..x.length]); @@ -3710,6 +3724,8 @@ template mapTrieIndex(Prefix...) @trusted struct TrieBuilder(Value, Key, Args...) if(isBitPackableType!Value && isValidArgsForTrie!(Key, Args)) { + import std.exception : enforce; + private: // last index is not stored in table, it is used as an offset to values in a block. static if(is(Value == bool))// always pack bool @@ -3965,6 +3981,7 @@ public: */ void putValue(Key key, Value v) { + import std.conv : text; auto idx = getIndex(key); enforce(idx >= curIndex, text(errMsg, " ", idx)); putAt(idx, v); @@ -4279,6 +4296,7 @@ public template codepointTrie(T, sizes...) unittest // codepointTrie example { + import std.algorithm; // pick characters from the Greek script auto set = unicode.Greek; @@ -4390,7 +4408,7 @@ public template buildTrie(Value, Key, Args...) In other words $(LREF mapTrieIndex) should be a monotonically increasing function that maps $(D Key) to an integer. - See also: $(XREF _algorithm, sort), + See_Also: $(XREF _algorithm, sort), $(XREF _range, SortedRange), $(XREF _algorithm, setUnion). */ @@ -4430,6 +4448,7 @@ public template buildTrie(Value, Key, Args...) && is(typeof(Range.init.front[0]) : Value) && is(typeof(Range.init.front[1]) : Key)) { + import std.algorithm : multiSort; alias Comps = GetComparators!(Prefix.length); if(unsorted) multiSort!(Comps)(range); @@ -4473,6 +4492,7 @@ public template buildTrie(Value, Key, Args...) */ auto buildTrie(Key, Value)(Value[Key] map, Value filler=Value.init) { + import std.range : zip, array; auto range = array(zip(map.values, map.keys)); return buildTrie(range, filler, true); // sort it } @@ -4627,7 +4647,7 @@ enum Mode { alwaysSkip, neverSkip, skipOnMatch -}; +} mixin template ForwardStrings() { @@ -4705,6 +4725,7 @@ template Utf8Matcher() auto build(Set)(Set set) { + import std.algorithm : map; auto ascii = set & unicode.ASCII; auto utf8_2 = set & CodepointSet(0x80, 0x800); auto utf8_3 = set & CodepointSet(0x800, 0x1_0000); @@ -4721,7 +4742,7 @@ template Utf8Matcher() // from 3 primitives: tab!(size), lookup and Sizes mixin template DefMatcher() { - import std.string : format; + import std.format : format; enum hasASCII = staticIndexOf!(1, Sizes) >= 0; alias UniSizes = Erase!(1, Sizes); @@ -4956,6 +4977,7 @@ template Utf16Matcher() auto build(Set)(Set set) { + import std.algorithm : map; auto ascii = set & unicode.ASCII; auto bmp = (set & CodepointSet.fromIntervals(0x80, 0xFFFF+1)) - CodepointSet.fromIntervals(0xD800, 0xDFFF+1); @@ -5305,6 +5327,7 @@ package auto units(C)(C[] s) @safe unittest { + import std.range; static bool testAll(Matcher, Range)(ref Matcher m, ref Range r) { bool t = m.test(r); @@ -5354,7 +5377,9 @@ package auto units(C)(C[] s) // cover decode fail cases of Matcher unittest { - import std.string : format; + import std.exception : collectException; + import std.format : format; + import std.algorithm; auto utf16 = utfMatcher!wchar(unicode.L); auto utf8 = utfMatcher!char(unicode.L); //decode failure cases UTF-8 @@ -5561,6 +5586,9 @@ template Sequence(size_t start, size_t end) //---- TRIE TESTS ---- unittest { + import std.conv; + import std.algorithm; + import std.range; static trieStats(TRIE)(TRIE t) { version(std_uni_stats) @@ -5703,13 +5731,17 @@ template idxTypes(Key, size_t fullBits, Prefix...) @trusted int comparePropertyName(Char1, Char2)(const(Char1)[] a, const(Char2)[] b) { - alias low = std.ascii.toLower; + import std.ascii : toLower; + import std.algorithm : cmp, map, filter; + static bool pred(dchar c) {return !c.isWhite && c != '-' && c != '_';} return cmp( - a.map!(x => low(x))() - .filter!(x => !isWhite(x) && x != '-' && x != '_')(), - b.map!(x => low(x))() - .filter!(x => !isWhite(x) && x != '-' && x != '_')() - ); + a.map!toLower.filter!pred, + b.map!toLower.filter!pred); +} + +unittest +{ + assert(!comparePropertyName("foo-bar", "fooBar")); } bool propertyNameLess(Char1, Char2)(const(Char1)[] a, const(Char2)[] b) @@ -5742,6 +5774,7 @@ bool propertyNameLess(Char1, Char2)(const(Char1)[] a, const(Char2)[] b) @safe uint decompressFrom(const(ubyte)[] arr, ref size_t idx) pure { + import std.exception : enforce; uint first = arr[idx++]; if(!(first & 0x80)) // no top bit -> [0..127] return first; @@ -5776,6 +5809,7 @@ package ubyte[] compressIntervals(Range)(Range intervals) unittest { + import std.typecons; auto run = [tuple(80, 127), tuple(128, (1<<10)+128)]; ubyte[] enc = [cast(ubyte)80, 47, 1, (0b1_00<<5) | (1<<2), 0]; assert(compressIntervals(run) == enc); @@ -5856,6 +5890,8 @@ else // helper for looking up code point sets @trusted ptrdiff_t findUnicodeSet(alias table, C)(in C[] name) pure { + import std.range : assumeSorted; + import std.algorithm : map; auto range = assumeSorted!((a,b) => propertyNameLess(a,b)) (table.map!"a.name"()); size_t idx = range.lowerBound(name).length; @@ -5976,6 +6012,7 @@ else // CTFE-only helper for checking property names at compile-time @safe bool isPrettyPropertyName(C)(in C[] name) { + import std.algorithm : find; auto names = [ "L", "Letter", "LC", "Cased Letter", @@ -6004,6 +6041,7 @@ template SetSearcher(alias table, string kind) static auto opCall(C)(in C[] name) if(is(C : dchar)) { + import std.conv : to; CodepointSet set; if(loadUnicodeSet!table(name, set)) return set; @@ -6047,7 +6085,7 @@ template SetSearcher(alias table, string kind) $(D unicode.InBlockName), to search a script use $(D unicode.ScriptName). - See also $(LREF block), $(LREF script) + See_Also: $(LREF block), $(LREF script) and (not included in this search) $(LREF hangulSyllableType). Example: @@ -6098,8 +6136,6 @@ template SetSearcher(alias table, string kind) /** Narrows down the search for sets of $(CODEPOINTS) to all Unicode blocks. - See also $(S_LINK Unicode properties, table of properties). - Note: Here block names are unambiguous as no scripts are searched and thus to search use simply $(D unicode.block.BlockName) notation. @@ -6111,6 +6147,8 @@ template SetSearcher(alias table, string kind) // use .block for explicitness assert(unicode.block.Greek_and_Coptic == unicode.InGreek_and_Coptic); --- + + See_Also: $(S_LINK Unicode properties, table of properties). */ struct block { @@ -6179,6 +6217,7 @@ private: static auto loadAny(Set=CodepointSet, C)(in C[] name) pure { + import std.conv : to; Set set; bool loaded = loadProperty(name, set) || loadUnicodeSet!(scripts.tab)(name, set) || (name.length > 2 && ucmp(name[0..2],"In") == 0 @@ -6195,6 +6234,7 @@ private: unittest { + import std.exception : collectException; auto ascii = unicode.ASCII; assert(ascii['A']); assert(ascii['~']); @@ -6249,7 +6289,7 @@ enum controlSwitch = ` // TODO: redo the most of hangul stuff algorithmically in case of Graphemes too // kill unrolled switches -private static bool isRegionalIndicator(dchar ch) +private static bool isRegionalIndicator(dchar ch) @safe { return ch >= '\U0001F1E6' && ch <= '\U0001F1FF'; } @@ -6499,6 +6539,9 @@ auto byGrapheme(Range)(Range range) /// unittest { + import std.conv; + import std.range; + import std.algorithm; auto text = "noe\u0308l"; // noël using e + combining diaeresis assert(text.walkLength == 5); // 5 code points @@ -6522,6 +6565,9 @@ private static struct InputRangeString unittest { + import std.conv; + import std.range; + import std.algorithm; assert("".byGrapheme.walkLength == 0); auto reverse = "le\u0308on"; @@ -6557,7 +6603,7 @@ unittest auto byCodePoint(Range)(Range range) if(isInputRange!Range && is(Unqual!(ElementType!Range) == Grapheme)) { - // TODO: Propagate bidirectional access + // TODO: Propagate bidirectional access static struct Result { private Range _range; @@ -6607,6 +6653,7 @@ Range byCodePoint(Range)(Range range) unittest { import std.conv : text; + import std.range; string s = "noe\u0308l"; // noël @@ -6622,6 +6669,8 @@ unittest unittest { + import std.conv; + import std.algorithm; assert("".byGrapheme.byCodePoint.equal("")); string text = "noe\u0308l"; @@ -6664,10 +6713,13 @@ unittest // the usual range manipulation is possible assert(wideOne[].filter!isMark.equal("\u0308")); --- - $(P See also $(LREF decodeGrapheme), $(LREF graphemeStride). ) + + See_Also: $(LREF decodeGrapheme), $(LREF graphemeStride) +/ @trusted struct Grapheme { + import std.exception : enforce; + public: this(C)(in C[] chars...) if(is(C : dchar)) @@ -6683,7 +6735,7 @@ public: } /// Gets a $(CODEPOINT) at the given index in this cluster. - dchar opIndex(size_t index) const pure nothrow + dchar opIndex(size_t index) const pure nothrow @nogc { assert(index < length); return read24(isBig ? ptr_ : small_.ptr, index); @@ -6706,7 +6758,7 @@ public: assert(!g.valid); --- +/ - void opIndexAssign(dchar ch, size_t index) pure nothrow + void opIndexAssign(dchar ch, size_t index) pure nothrow @nogc { assert(index < length); write24(isBig ? ptr_ : small_.ptr, ch, index); @@ -6718,19 +6770,19 @@ public: Warning: Invalidates when this Grapheme leaves the scope, attempts to use it then would lead to memory corruption. +/ - @system auto opSlice(size_t a, size_t b) pure nothrow + @system auto opSlice(size_t a, size_t b) pure nothrow @nogc { return sliceOverIndexed(a, b, &this); } /// ditto - @system auto opSlice() pure nothrow + @system auto opSlice() pure nothrow @nogc { return sliceOverIndexed(0, length, &this); } /// Grapheme cluster length in $(CODEPOINTS). - @property size_t length() const pure nothrow + @property size_t length() const pure nothrow @nogc { return isBig ? len_ : slen_ & 0x7F; } @@ -6754,7 +6806,7 @@ public: // still could be useful though assert(g[].equal("A\u0301B")); --- - See also $(LREF Grapheme.valid) below. + See_Also: $(LREF Grapheme.valid) +/ ref opOpAssign(string op)(dchar ch) { @@ -6873,13 +6925,13 @@ private: setBig(); } - void setBig(){ slen_ |= small_flag; } + void setBig() pure nothrow @nogc { slen_ |= small_flag; } - @property size_t smallLength() pure nothrow + @property size_t smallLength() const pure nothrow @nogc { return slen_ & small_mask; } - @property ubyte isBig() const pure nothrow + @property ubyte isBig() const pure nothrow @nogc { return slen_ & small_flag; } @@ -6933,6 +6985,10 @@ unittest unittest { + import std.conv; + import std.algorithm; + import std.range; + // not valid clusters (but it just a test) auto g = Grapheme('a', 'b', 'c', 'd', 'e'); assert(g[0] == 'a'); @@ -7056,6 +7112,7 @@ int sicmp(S1, S2)(S1 str1, S2 str2) private int fullCasedCmp(Range)(dchar lhs, dchar rhs, ref Range rtail) @trusted pure /*TODO nothrow*/ { + import std.algorithm : skipOver; alias fTable = fullCaseTable; size_t idx = fullCaseTrie[lhs]; // fullCaseTrie is packed index table @@ -7148,13 +7205,16 @@ int icmp(S1, S2)(S1 str1, S2 str2) unittest { + import std.conv; + import std.exception : assertCTFEable; + import std.algorithm; assertCTFEable!( { foreach(cfunc; TypeTuple!(icmp, sicmp)) { foreach(S1; TypeTuple!(string, wstring, dstring)) foreach(S2; TypeTuple!(string, wstring, dstring)) - { + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 assert(cfunc("".to!S1(), "".to!S2()) == 0); assert(cfunc("A".to!S1(), "".to!S2()) > 0); assert(cfunc("".to!S1(), "0".to!S2()) < 0); @@ -7166,7 +7226,7 @@ unittest // Check example: assert(cfunc("Август".to!S1(), "авгусТ".to!S2()) == 0); assert(cfunc("ΌΎ".to!S1(), "όύ".to!S2()) == 0); - } + }(); // check that the order is properly agnostic to the case auto strs = [ "Apple", "ORANGE", "orAcle", "amp", "banana"]; sort!((a,b) => cfunc(a,b) < 0)(strs); @@ -7265,6 +7325,9 @@ package auto simpleCaseFoldings(dchar ch) unittest { + import std.exception : assertCTFEable; + import std.algorithm : canFind; + import std.array; assertCTFEable!((){ auto r = simpleCaseFoldings('Э').array; assert(r.length == 2); @@ -7350,9 +7413,11 @@ enum { assert(compose('\u0308', 'A') == dchar.init); --- +/ -public dchar compose(dchar first, dchar second) +public dchar compose(dchar first, dchar second) pure nothrow { import std.internal.unicode_comp; + import std.algorithm : map; + import std.range : assumeSorted; size_t packed = compositionJumpTrie[first]; if(packed == ushort.max) return dchar.init; @@ -7379,7 +7444,8 @@ public dchar compose(dchar first, dchar second) Note: This function also decomposes hangul syllables as prescribed by the standard. - See also $(LREF decomposeHangul) for a restricted version + + See_Also: $(LREF decomposeHangul) for a restricted version that takes into account only hangul syllables but no other decompositions. @@ -7395,6 +7461,7 @@ public dchar compose(dchar first, dchar second) public Grapheme decompose(UnicodeDecomposition decompType=Canonical)(dchar ch) { import std.internal.unicode_decomp; + import std.algorithm : until; static if(decompType == Canonical) { alias table = decompCanonTable; @@ -7440,14 +7507,14 @@ enum jamoNCount = jamoVCount * jamoTCount; enum jamoSCount = jamoLCount * jamoNCount; // Tests if $(D ch) is a Hangul leading consonant jamo. -bool isJamoL(dchar ch) +bool isJamoL(dchar ch) pure nothrow @nogc { // first cmp rejects ~ 1M code points above leading jamo range return ch < jamoLBase+jamoLCount && ch >= jamoLBase; } // Tests if $(D ch) is a Hangul vowel jamo. -bool isJamoT(dchar ch) +bool isJamoT(dchar ch) pure nothrow @nogc { // first cmp rejects ~ 1M code points above trailing jamo range // Note: ch == jamoTBase doesn't indicate trailing jamo (TIndex must be > 0) @@ -7455,20 +7522,20 @@ bool isJamoT(dchar ch) } // Tests if $(D ch) is a Hangul trailnig consonant jamo. -bool isJamoV(dchar ch) +bool isJamoV(dchar ch) pure nothrow @nogc { // first cmp rejects ~ 1M code points above vowel range return ch < jamoVBase+jamoVCount && ch >= jamoVBase; } -int hangulSyllableIndex(dchar ch) +int hangulSyllableIndex(dchar ch) pure nothrow @nogc { int idxS = cast(int)ch - jamoSBase; return idxS >= 0 && idxS < jamoSCount ? idxS : -1; } // internal helper: compose hangul syllables leaving dchar.init in holes -void hangulRecompose(dchar[] seq) +void hangulRecompose(dchar[] seq) pure nothrow @nogc { for(size_t idx = 0; idx + 1 < seq.length; ) { @@ -7502,12 +7569,6 @@ public: /** Decomposes a Hangul syllable. If $(D ch) is not a composed syllable then this function returns $(LREF Grapheme) containing only $(D ch) as is. - - Example: - --- - import std.algorithm; - assert(decomposeHangul('\uD4DB')[].equal("\u1111\u1171\u11B6")); - --- */ Grapheme decomposeHangul(dchar ch) { @@ -7525,6 +7586,13 @@ Grapheme decomposeHangul(dchar ch) return Grapheme(partL, partV); } +/// +unittest +{ + import std.algorithm; + assert(decomposeHangul('\uD4DB')[].equal("\u1111\u1171\u11B6")); +} + /++ Try to compose hangul syllable out of a leading consonant ($(D lead)), a $(D vowel) and optional $(D trailing) consonant jamos. @@ -7533,19 +7601,8 @@ Grapheme decomposeHangul(dchar ch) If any of $(D lead) and $(D vowel) are not a valid hangul jamo of the respective $(CHARACTER) class returns dchar.init. - - Example: - --- - assert(composeJamo('\u1111', '\u1171', '\u11B6') == '\uD4DB'); - // leaving out T-vowel, or passing any codepoint - // that is not trailing consonant composes an LV-syllable - assert(composeJamo('\u1111', '\u1171') == '\uD4CC'); - assert(composeJamo('\u1111', '\u1171', ' ') == '\uD4CC'); - assert(composeJamo('\u1111', 'A') == dchar.init); - assert(composeJamo('A', '\u1171') == dchar.init); - --- +/ -dchar composeJamo(dchar lead, dchar vowel, dchar trailing=dchar.init) +dchar composeJamo(dchar lead, dchar vowel, dchar trailing=dchar.init) pure nothrow @nogc { if(!isJamoL(lead)) return dchar.init; @@ -7558,8 +7615,22 @@ dchar composeJamo(dchar lead, dchar vowel, dchar trailing=dchar.init) return isJamoT(trailing) ? syllable + (trailing - jamoTBase) : syllable; } +/// +unittest +{ + assert(composeJamo('\u1111', '\u1171', '\u11B6') == '\uD4DB'); + // leaving out T-vowel, or passing any codepoint + // that is not trailing consonant composes an LV-syllable + assert(composeJamo('\u1111', '\u1171') == '\uD4CC'); + assert(composeJamo('\u1111', '\u1171', ' ') == '\uD4CC'); + assert(composeJamo('\u1111', 'A') == dchar.init); + assert(composeJamo('A', '\u1171') == dchar.init); +} + unittest { + import std.conv; + static void testDecomp(UnicodeDecomposition T)(dchar ch, string r) { Grapheme g = decompose!T(ch); @@ -7614,23 +7685,13 @@ enum { Note: In cases where the string in question is already normalized, it is returned unmodified and no memory allocation happens. - - Example: - --- - // any encoding works - wstring greet = "Hello world"; - assert(normalize(greet) is greet); // the same exact slice - - // An example of a character with all 4 forms being different: - // Greek upsilon with acute and hook symbol (code point 0x03D3) - assert(normalize!NFC("ϓ") == "\u03D3"); - assert(normalize!NFD("ϓ") == "\u03D2\u0301"); - assert(normalize!NFKC("ϓ") == "\u038E"); - assert(normalize!NFKD("ϓ") == "\u03A5\u0301"); - --- +/ inout(C)[] normalize(NormalizationForm norm=NFC, C)(inout(C)[] input) { + import std.algorithm : sort, SwapStrategy; + import std.range : zip; + import std.array : appender; + auto anchors = splitNormalized!norm(input); if(anchors[0] == input.length && anchors[1] == input.length) return input; @@ -7679,6 +7740,7 @@ inout(C)[] normalize(NormalizationForm norm=NFC, C)(inout(C)[] input) (zip(ccc[firstNonStable..$], decomposed[firstNonStable..$])); static if(norm == NFC || norm == NFKC) { + import std.algorithm : countUntil; size_t idx = 0; auto first = countUntil(ccc, 0); if(first >= 0) // no starters?? no recomposition @@ -7698,6 +7760,7 @@ inout(C)[] normalize(NormalizationForm norm=NFC, C)(inout(C)[] input) app.put(decomposed); else { + import std.algorithm : remove; auto clean = remove!("a == dchar.init", SwapStrategy.stable)(decomposed); app.put(decomposed[0 .. clean.length]); } @@ -7714,8 +7777,25 @@ inout(C)[] normalize(NormalizationForm norm=NFC, C)(inout(C)[] input) return cast(inout(C)[])app.data; } +/// +unittest +{ + // any encoding works + wstring greet = "Hello world"; + assert(normalize(greet) is greet); // the same exact slice + + // An example of a character with all 4 forms being different: + // Greek upsilon with acute and hook symbol (code point 0x03D3) + assert(normalize!NFC("ϓ") == "\u03D3"); + assert(normalize!NFD("ϓ") == "\u03D2\u0301"); + assert(normalize!NFKC("ϓ") == "\u038E"); + assert(normalize!NFKD("ϓ") == "\u03A5\u0301"); +} + unittest { + import std.conv; + assert(normalize!NFD("abc\uF904def") == "abc\u6ED1def", text(normalize!NFD("abc\uF904def"))); assert(normalize!NFKD("2¹⁰") == "210", normalize!NFKD("2¹⁰")); assert(normalize!NFD("Äffin") == "A\u0308ffin"); @@ -7735,7 +7815,7 @@ unittest } // canonically recompose given slice of code points, works in-place and mutates data -private size_t recompose(size_t start, dchar[] input, ubyte[] ccc) +private size_t recompose(size_t start, dchar[] input, ubyte[] ccc) pure nothrow { assert(input.length == ccc.length); int accumCC = -1;// so that it's out of 0..255 range @@ -7792,6 +7872,7 @@ private size_t recompose(size_t start, dchar[] input, ubyte[] ccc) // the rest of input starting with stable code point private auto splitNormalized(NormalizationForm norm, C)(const(C)[] input) { + import std.typecons : tuple; auto result = input; ubyte lastCC = 0; @@ -7821,6 +7902,7 @@ private auto splitNormalized(NormalizationForm norm, C)(const(C)[] input) private auto seekStable(NormalizationForm norm, C)(size_t idx, in C[] input) { import std.utf : codeLength; + import std.typecons : tuple; auto br = input[0..idx]; size_t region_start = 0;// default @@ -7853,18 +7935,21 @@ private auto seekStable(NormalizationForm norm, C)(size_t idx, in C[] input) /** Tests if dchar $(D ch) is always allowed (Quick_Check=YES) in normalization form $(D norm). - --- +*/ +public bool allowedIn(NormalizationForm norm)(dchar ch) +{ + return !notAllowedIn!norm(ch); +} + +/// +unittest +{ // e.g. Cyrillic is always allowed, so is ASCII assert(allowedIn!NFC('я')); assert(allowedIn!NFD('я')); assert(allowedIn!NFKC('я')); assert(allowedIn!NFKD('я')); assert(allowedIn!NFC('Z')); - --- -*/ -public bool allowedIn(NormalizationForm norm)(dchar ch) -{ - return !notAllowedIn!norm(ch); } // not user friendly name but more direct @@ -7900,7 +7985,8 @@ version(std_uni_bootstrap) // up to date optimal versions of all of isXXX functions @safe pure nothrow @nogc public bool isWhite(dchar c) { - return std.ascii.isWhite(c) || + import std.ascii : isWhite; + return isWhite(c) || c == lineSep || c == paraSep || c == '\u0085' || c == '\u00A0' || c == '\u1680' || c == '\u180E' || (c >= '\u2000' && c <= '\u200A') || @@ -7911,7 +7997,7 @@ else { // trusted -> avoid bounds check -@trusted pure nothrow +@trusted pure nothrow @nogc { // hide template instances behind functions (Bugzilla 13232) ushort toLowerIndex(dchar c) { return toLowerIndexTrie[c]; } @@ -7943,49 +8029,53 @@ public bool isWhite(dchar c) /++ Return whether $(D c) is a Unicode lowercase $(CHARACTER). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isLower(dchar c) { - if(std.ascii.isASCII(c)) - return std.ascii.isLower(c); + import std.ascii : isLower, isASCII; + if(isASCII(c)) + return isLower(c); return lowerCaseTrie[c]; } @safe unittest { + import std.ascii : isLower; foreach(v; 0..0x80) - assert(std.ascii.isLower(v) == isLower(v)); - assert(isLower('я')); - assert(isLower('й')); - assert(!isLower('Ж')); + assert(isLower(v) == .isLower(v)); + assert(.isLower('я')); + assert(.isLower('й')); + assert(!.isLower('Ж')); // Greek HETA - assert(!isLower('\u0370')); - assert(isLower('\u0371')); - assert(!isLower('\u039C')); // capital MU - assert(isLower('\u03B2')); // beta + assert(!.isLower('\u0370')); + assert(.isLower('\u0371')); + assert(!.isLower('\u039C')); // capital MU + assert(.isLower('\u03B2')); // beta // from extended Greek - assert(!isLower('\u1F18')); - assert(isLower('\u1F00')); + assert(!.isLower('\u1F18')); + assert(.isLower('\u1F00')); foreach(v; unicode.lowerCase.byCodepoint) - assert(isLower(v) && !isUpper(v)); + assert(.isLower(v) && !isUpper(v)); } /++ Return whether $(D c) is a Unicode uppercase $(CHARACTER). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isUpper(dchar c) { - if(std.ascii.isASCII(c)) - return std.ascii.isUpper(c); + import std.ascii : isUpper, isASCII; + if(isASCII(c)) + return isUpper(c); return upperCaseTrie[c]; } @safe unittest { + import std.ascii : isLower; foreach(v; 0..0x80) - assert(std.ascii.isLower(v) == isLower(v)); + assert(isLower(v) == .isLower(v)); assert(!isUpper('й')); assert(isUpper('Ж')); // Greek HETA @@ -7997,7 +8087,7 @@ bool isUpper(dchar c) assert(!isUpper('\u1F00')); assert(isUpper('\u1F18')); foreach(v; unicode.upperCase.byCodepoint) - assert(isUpper(v) && !isLower(v)); + assert(isUpper(v) && !.isLower(v)); } @@ -8008,7 +8098,7 @@ bool isUpper(dchar c) Warning: certain alphabets like German and Greek have no 1:1 upper-lower mapping. Use overload of toLower which takes full string instead. +/ -@safe pure nothrow +@safe pure nothrow @nogc dchar toLower(dchar c) { // optimize ASCII case @@ -8030,7 +8120,7 @@ dchar toLower(dchar c) //TODO: Hidden for now, needs better API. //Other transforms could use better API as well, but this one is a new primitive. -@safe pure nothrow +@safe pure nothrow @nogc private dchar toTitlecase(dchar c) { // optimize ASCII case @@ -8057,6 +8147,8 @@ private alias LowerTriple = TypeTuple!(toLowerIndex, MAX_SIMPLE_LOWER, toLowerTa private S toCase(alias indexFn, uint maxIdx, alias tableFn, S)(S s) @trusted pure if(isSomeString!S) { + import std.array : appender; + foreach(i, dchar cOuter; s) { ushort idx = indexFn(cOuter); @@ -8091,6 +8183,7 @@ private S toCase(alias indexFn, uint maxIdx, alias tableFn, S)(S s) @trusted pur unittest //12428 { + import std.array; auto s = "abcdefghij".replicate(300); s = s[0..10]; @@ -8100,7 +8193,7 @@ unittest //12428 } // TODO: helper, I wish std.utf was more flexible (and stright) -private size_t encodeTo(char[] buf, size_t idx, dchar c) @trusted pure +private size_t encodeTo(char[] buf, size_t idx, dchar c) @trusted pure nothrow @nogc { if (c <= 0x7F) { @@ -8166,7 +8259,7 @@ private size_t encodeTo(wchar[] buf, size_t idx, dchar c) @trusted pure return idx; } -private size_t encodeTo(dchar[] buf, size_t idx, dchar c) @trusted pure +private size_t encodeTo(dchar[] buf, size_t idx, dchar c) @trusted pure nothrow @nogc { buf[idx] = c; idx++; @@ -8413,7 +8506,7 @@ S toLower(S)(S s) @trusted pure @trusted unittest //@@@BUG std.format is not @safe { - import std.string : format; + import std.format : format; foreach(ch; 0..0x80) assert(std.ascii.toLower(ch) == toLower(ch)); assert(toLower('Я') == 'я'); @@ -8441,6 +8534,7 @@ unittest unittest { + import std.algorithm : cmp; string s1 = "FoL"; string s2 = toLower(s1); assert(cmp(s2, "fol") == 0, s2); @@ -8496,8 +8590,12 @@ unittest Warning: Certain alphabets like German and Greek have no 1:1 upper-lower mapping. Use overload of toUpper which takes full string instead. + + toUpper can be used as an argument to $(XREF algorithm, map) to produce an algorithm that can + convert a range of characters to upper case without allocating memory. + A string can then be produced by using $(XREF algorithm, copy) to send it to an $(XREF array, appender). +/ -@safe pure nothrow +@safe pure nothrow @nogc dchar toUpper(dchar c) { // optimize ASCII case @@ -8517,9 +8615,21 @@ dchar toUpper(dchar c) return c; } +/// +unittest +{ + import std.algorithm; + import std.uni; + import std.array; + + auto abuf = appender!(char[])(); + "hello".map!toUpper.copy(&abuf); + assert(abuf.data == "HELLO"); +} + @trusted unittest { - import std.string : format; + import std.format : format; foreach(ch; 0..0x80) assert(std.ascii.toUpper(ch) == toUpper(ch)); assert(toUpper('я') == 'Я'); @@ -8556,6 +8666,8 @@ S toUpper(S)(S s) @trusted pure unittest { + import std.algorithm : cmp; + string s1 = "FoL"; string s2; char[] s3; @@ -8585,7 +8697,7 @@ unittest { static void doTest(C)(const(C)[] s, const(C)[] trueUp, const(C)[] trueLow) { - import std.string : format; + import std.format : format; string diff = "src: %( %x %)\nres: %( %x %)\ntru: %( %x %)"; auto low = s.toLower() , up = s.toUpper(); auto lowInp = s.dup, upInp = s.dup; @@ -8643,7 +8755,7 @@ unittest Returns whether $(D c) is a Unicode alphabetic $(CHARACTER) (general Unicode category: Alphabetic). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isAlpha(dchar c) { // optimization @@ -8678,7 +8790,7 @@ bool isAlpha(dchar c) Returns whether $(D c) is a Unicode mark (general Unicode category: Mn, Me, Mc). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isMark(dchar c) { return markTrie[c]; @@ -8697,7 +8809,7 @@ bool isMark(dchar c) Returns whether $(D c) is a Unicode numerical $(CHARACTER) (general Unicode category: Nd, Nl, No). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isNumber(dchar c) { return numberTrie[c]; @@ -8717,7 +8829,7 @@ bool isNumber(dchar c) Returns whether $(D c) is a Unicode punctuation $(CHARACTER) (general Unicode category: Pd, Ps, Pe, Pc, Po, Pi, Pf). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isPunctuation(dchar c) { return punctuationTrie[c]; @@ -8740,7 +8852,7 @@ unittest Returns whether $(D c) is a Unicode symbol $(CHARACTER) (general Unicode category: Sm, Sc, Sk, So). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isSymbol(dchar c) { return symbolTrie[c]; @@ -8748,7 +8860,7 @@ bool isSymbol(dchar c) unittest { - import std.string; + import std.format : format; assert(isSymbol('\u0024')); assert(isSymbol('\u002B')); assert(isSymbol('\u005E')); @@ -8763,7 +8875,7 @@ unittest Note: This doesn't include '\n', '\r', \t' and other non-space $(CHARACTER). For commonly used less strict semantics see $(LREF isWhite). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isSpace(dchar c) { return isSpaceGen(c); @@ -8785,7 +8897,7 @@ unittest (general Unicode category: L, M, N, P, S, Zs). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isGraphical(dchar c) { return graphicalTrie[c]; @@ -8795,7 +8907,7 @@ bool isGraphical(dchar c) unittest { auto set = unicode("Graphical"); - import std.string; + import std.format : format; foreach(ch; set.byCodepoint) assert(isGraphical(ch), format("%4x", ch)); foreach(ch; 0..0x4000) @@ -8807,7 +8919,7 @@ unittest Returns whether $(D c) is a Unicode control $(CHARACTER) (general Unicode category: Cc). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isControl(dchar c) { return isControlGen(c); @@ -8830,7 +8942,7 @@ unittest Returns whether $(D c) is a Unicode formatting $(CHARACTER) (general Unicode category: Cf). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isFormat(dchar c) { return isFormatGen(c); @@ -8851,7 +8963,7 @@ unittest Returns whether $(D c) is a Unicode Private Use $(CODEPOINT) (general Unicode category: Co). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isPrivateUse(dchar c) { return (0x00_E000 <= c && c <= 0x00_F8FF) @@ -8863,7 +8975,7 @@ bool isPrivateUse(dchar c) Returns whether $(D c) is a Unicode surrogate $(CODEPOINT) (general Unicode category: Cs). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isSurrogate(dchar c) { return (0xD800 <= c && c <= 0xDFFF); @@ -8872,7 +8984,7 @@ bool isSurrogate(dchar c) /++ Returns whether $(D c) is a Unicode high surrogate (lead surrogate). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isSurrogateHi(dchar c) { return (0xD800 <= c && c <= 0xDBFF); @@ -8881,7 +8993,7 @@ bool isSurrogateHi(dchar c) /++ Returns whether $(D c) is a Unicode low surrogate (trail surrogate). +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isSurrogateLo(dchar c) { return (0xDC00 <= c && c <= 0xDFFF); @@ -8892,7 +9004,7 @@ bool isSurrogateLo(dchar c) a $(CODEPOINT) with no assigned abstract character. (general Unicode category: Cn) +/ -@safe pure nothrow +@safe pure nothrow @nogc bool isNonCharacter(dchar c) { return nonCharacterTrie[c]; @@ -8919,7 +9031,7 @@ private: return const(CodepointTrie!T)(e.offsets, e.sizes, e.data); } -@safe pure nothrow @property +@safe pure nothrow @nogc @property { // It's important to use auto return here, so that the compiler // only runs semantic on the return type if the function gets diff --git a/std/uri.d b/std/uri.d index 1aa5f21870e..ce79f36cab0 100644 --- a/std/uri.d +++ b/std/uri.d @@ -15,7 +15,7 @@ * WIKI = Phobos/StdUri * * Copyright: Copyright Digital Mars 2000 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * Source: $(PHOBOSSRC std/_uri.d) */ @@ -32,26 +32,27 @@ debug(uri) private import std.stdio; /* ====================== URI Functions ================ */ private import std.ascii; -private import std.c.stdlib; +private import core.stdc.stdlib; private import std.utf; private import std.traits : isSomeChar; import core.exception : OutOfMemoryError; import std.exception : assumeUnique; +/** This Exception is thrown if something goes wrong when encoding or +decoding a URI. +*/ class URIException : Exception { - @safe pure nothrow this() + import std.array : empty; + @safe pure nothrow this(string msg, string file = __FILE__, + size_t line = __LINE__, Throwable next = null) { - super("URI Exception"); - } - - @safe pure nothrow this(string msg) - { - super("URI Exception: " ~ msg); + super("URI Exception" ~ (!msg.empty ? ": " ~ msg : ""), file, line, + next); } } -enum +private enum { URI_Alpha = 1, URI_Reserved = 2, @@ -397,19 +398,19 @@ size_t uriLength(Char)(in Char[] s) if (isSomeChar!Char) * https:// * www. */ - import std.string : icmp; + import std.uni : icmp; size_t i; if (s.length <= 4) return -1; - if (s.length > 7 && std.string.icmp(s[0 .. 7], "http://") == 0) { + if (s.length > 7 && icmp(s[0 .. 7], "http://") == 0) { i = 7; } else { - if (s.length > 8 && std.string.icmp(s[0 .. 8], "https://") == 0) + if (s.length > 8 && icmp(s[0 .. 8], "https://") == 0) i = 8; else return -1; @@ -442,6 +443,7 @@ size_t uriLength(Char)(in Char[] s) if (isSomeChar!Char) return i; } +/// unittest { string s1 = "http://www.digitalmars.com/~fred/fredsRX.html#foo end!"; @@ -504,6 +506,7 @@ size_t emailLength(Char)(in Char[] s) if (isSomeChar!Char) return i; } +/// unittest { string s1 = "my.e-mail@www.example-domain.com with garbage added"; diff --git a/std/utf.d b/std/utf.d index cbe867b8412..7f3cb90df82 100644 --- a/std/utf.d +++ b/std/utf.d @@ -20,9 +20,7 @@ +/ module std.utf; -import std.conv; // to, assumeUnique -import std.exception; // enforce, assumeUnique -import std.range; // walkLength +import std.range.primitives; import std.traits; // isSomeChar, isSomeString import std.typetuple; // TypeTuple @@ -30,29 +28,23 @@ import std.typetuple; // TypeTuple debug (utf) import core.stdc.stdio : printf; -version(unittest) -{ - import core.exception; - import std.string; -} - /++ Exception thrown on errors in std.utf functions. +/ class UTFException : Exception { + import std.string : format; + uint[4] sequence; size_t len; @safe pure nothrow @nogc UTFException setSequence(uint[] data...) { - import std.algorithm; - assert(data.length <= 4); - len = min(data.length, 4); + len = data.length < 4 ? data.length : 4; sequence[0 .. len] = data[0 .. len]; return this; @@ -67,14 +59,12 @@ class UTFException : Exception @safe pure this(string msg, size_t index, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { - import std.string; super(msg ~ format(" (at index %s)", index), file, line, next); } override string toString() { - import std.string; if (len == 0) return super.toString(); @@ -116,6 +106,7 @@ pure nothrow bool isValidDchar(dchar c) @nogc unittest { + import std.exception; debug(utf) printf("utf.isValidDchar.unittest\n"); assertCTFEable!( @@ -147,7 +138,8 @@ unittest $(D index) defaults to $(D 0) if none is passed. Returns: - The number of bytes in the UTF-8 sequence. + The number of bytes in the UTF-8 sequence, a value between 1 and 4 + (as per $(WEB tools.ietf.org/html/rfc3629#section-3, RFC 3629$(COMMA) section 3)). Throws: May throw a $(D UTFException) if $(D str[index]) is not the start of a @@ -193,15 +185,19 @@ private uint strideImpl(char c, size_t index) @trusted pure in { assert(c & 0x80); } body { - import core.bitop; + import core.bitop : bsr; immutable msbs = 7 - bsr(~c); - if (msbs < 2 || msbs > 6) + if (!~c || msbs < 2 || msbs > 4) throw new UTFException("Invalid UTF-8 sequence", index); return msbs; } unittest { + import std.conv : to; + import std.exception; + import std. string : format; + import core.exception : AssertError; static void test(string s, dchar c, size_t i = 0, size_t line = __LINE__) { enforce(stride(s, i) == codeLength!char(c), @@ -263,6 +259,20 @@ unittest }); } +unittest // invalid start bytes +{ + import std.exception: assertThrown; + immutable char[] invalidStartBytes = [ + 0b1111_1000, // indicating a sequence length of 5 + 0b1111_1100, // 6 + 0b1111_1110, // 7 + 0b1111_1111, // 8 + 0b1000_0000, // continuation byte + ]; + foreach(c; invalidStartBytes) + assertThrown!UTFException(stride([c])); +} + /++ $(D strideBack) returns the length of the UTF-8 sequence ending one code @@ -342,6 +352,10 @@ uint strideBack(S)(auto ref S str) unittest { + import std.conv : to; + import std.exception; + import std. string : format; + import core.exception : AssertError; static void test(string s, dchar c, size_t i = size_t.max, size_t line = __LINE__) { enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!char(c), @@ -454,6 +468,10 @@ uint stride(S)(auto ref S str) @trusted unittest { + import std.conv : to; + import std.exception; + import std. string : format; + import core.exception : AssertError; static void test(wstring s, dchar c, size_t i = 0, size_t line = __LINE__) { enforce(stride(s, i) == codeLength!wchar(c), @@ -570,6 +588,10 @@ uint strideBack(S)(auto ref S str) unittest { + import std.conv : to; + import std.exception; + import std. string : format; + import core.exception : AssertError; static void test(wstring s, dchar c, size_t i = size_t.max, size_t line = __LINE__) { enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!wchar(c), @@ -657,6 +679,10 @@ uint stride(S)(auto ref S str, size_t index = 0) unittest { + import std.conv : to; + import std.exception; + import std. string : format; + import core.exception : AssertError; static void test(dstring s, dchar c, size_t i = 0, size_t line = __LINE__) { enforce(stride(s, i) == codeLength!dchar(c), @@ -754,6 +780,10 @@ uint strideBack(S)(auto ref S str) unittest { + import std.conv : to; + import std.exception; + import std. string : format; + import core.exception : AssertError; static void test(dstring s, dchar c, size_t i = size_t.max, size_t line = __LINE__) { enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!dchar(c), @@ -936,7 +966,8 @@ body { if (str[index] < codeUnitLimit!S) return str[index++]; - return decodeImpl!true(str, index); + else + return decodeImpl!true(str, index); } dchar decode(S)(auto ref S str, ref size_t index) @trusted pure @@ -953,7 +984,8 @@ body { if (str[index] < codeUnitLimit!S) return str[index++]; - return decodeImpl!true(str, index); + else + return decodeImpl!true(str, index); } /++ @@ -991,18 +1023,20 @@ body numCodeUnits = 1; return fst; } + else + { + //@@@BUG@@@ 8521 forces canIndex to be done outside of decodeImpl, which + //is undesirable, since not all overloads of decodeImpl need it. So, it + //should be moved back into decodeImpl once bug# 8521 has been fixed. + enum canIndex = isRandomAccessRange!S && hasSlicing!S && hasLength!S; + immutable retval = decodeImpl!canIndex(str, numCodeUnits); - //@@@BUG@@@ 8521 forces canIndex to be done outside of decodeImpl, which - //is undesirable, since not all overloads of decodeImpl need it. So, it - //should be moved back into decodeImpl once bug# 8521 has been fixed. - enum canIndex = isRandomAccessRange!S && hasSlicing!S && hasLength!S; - immutable retval = decodeImpl!canIndex(str, numCodeUnits); - - // The other range types were already popped by decodeImpl. - static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S) - str = str[numCodeUnits .. str.length]; + // The other range types were already popped by decodeImpl. + static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S) + str = str[numCodeUnits .. str.length]; - return retval; + return retval; + } } dchar decodeFront(S)(ref S str, out size_t numCodeUnits) @trusted pure @@ -1024,10 +1058,12 @@ body str = str[1 .. $]; return retval; } - - immutable retval = decodeImpl!true(str, numCodeUnits); - str = str[numCodeUnits .. $]; - return retval; + else + { + immutable retval = decodeImpl!true(str, numCodeUnits); + str = str[numCodeUnits .. $]; + return retval; + } } /++ Ditto +/ @@ -1295,6 +1331,10 @@ version(unittest) private void testDecode(R)(R range, size_t expectedIndex, size_t line = __LINE__) { + import std.exception; + import std. string : format; + import core.exception : AssertError; + static if (hasLength!R) immutable lenBefore = range.length; @@ -1320,6 +1360,10 @@ version(unittest) private void testDecodeFront(R)(ref R range, size_t expectedNumCodeUnits, size_t line = __LINE__) { + import std.exception; + import std. string : format; + import core.exception : AssertError; + static if (hasLength!R) immutable lenBefore = range.length; @@ -1348,6 +1392,10 @@ version(unittest) private void testBothDecode(R)(R range, version(unittest) private void testBadDecode(R)(R range, size_t index, size_t line = __LINE__) { + import std.exception; + import std. string : format; + import core.exception : AssertError; + immutable initialIndex = index; static if (hasLength!R) @@ -1371,6 +1419,8 @@ version(unittest) private void testBadDecode(R)(R range, size_t index, size_t li unittest { + import std.conv : to; + import std.exception; debug(utf) printf("utf.decode.unittest\n"); assertCTFEable!( @@ -1433,6 +1483,8 @@ unittest unittest { + import std.conv : to; + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(to!wstring, InputCU!wchar, RandomCU!wchar, @@ -1473,6 +1525,8 @@ unittest unittest { + import std.conv : to; + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!(to!dstring, RandomCU!dchar, InputCU!dchar, @@ -1512,6 +1566,7 @@ unittest unittest { + import std.exception; assertCTFEable!( { foreach (S; TypeTuple!( char[], const( char)[], string, @@ -1530,6 +1585,7 @@ unittest unittest { + import std.exception; char[4] val; val[0] = 0b1111_0111; val[1] = 0b1011_1111; @@ -1591,6 +1647,7 @@ size_t encode(ref char[4] buf, dchar c) @safe pure unittest { + import std.exception; assertCTFEable!( { char[4] buf; @@ -1642,6 +1699,7 @@ size_t encode(ref wchar[2] buf, dchar c) @safe pure unittest { + import std.exception; assertCTFEable!( { wchar[2] buf; @@ -1722,6 +1780,7 @@ void encode(ref char[] str, dchar c) @safe pure unittest { + import std.exception; debug(utf) printf("utf.encode.unittest\n"); assertCTFEable!( @@ -1744,6 +1803,7 @@ unittest unittest { + import std.exception; assertCTFEable!( { char[] buf; @@ -1801,6 +1861,7 @@ void encode(ref wchar[] str, dchar c) @safe pure unittest { + import std.exception; assertCTFEable!( { wchar[] buf; @@ -1833,6 +1894,7 @@ void encode(ref dchar[] str, dchar c) @safe pure unittest { + import std.exception; assertCTFEable!( { dchar[] buf; @@ -1918,6 +1980,7 @@ size_t codeLength(C, InputRange)(InputRange input) /// unittest { + import std.conv : to; assert(codeLength!char("hello world") == to!string("hello world").length); assert(codeLength!wchar("hello world") == @@ -1940,6 +2003,8 @@ unittest unittest { + import std.conv : to; + import std.exception; import std.algorithm : filter; assertCTFEable!( @@ -2022,10 +2087,11 @@ void validate(S)(in S str) @safe pure unittest // bugzilla 12923 { - assertThrown((){ - char[3]a=[167, 133, 175]; - validate(a[]); - }()); + import std.exception; + assertThrown((){ + char[3]a=[167, 133, 175]; + validate(a[]); + }()); } /* =================== Conversion to UTF8 ======================= */ @@ -2033,7 +2099,7 @@ unittest // bugzilla 12923 pure { -char[] toUTF8(out char[4] buf, dchar c) nothrow @nogc @safe +char[] toUTF8(return out char[4] buf, dchar c) nothrow @nogc @safe in { assert(isValidDchar(c)); @@ -2083,6 +2149,8 @@ string toUTF8(in char[] s) @safe /// ditto string toUTF8(in wchar[] s) @trusted { + import std.exception : assumeUnique; + char[] r; size_t i; size_t slen = s.length; @@ -2109,6 +2177,8 @@ string toUTF8(in wchar[] s) @trusted /// ditto string toUTF8(in dchar[] s) @trusted { + import std.exception : assumeUnique; + char[] r; size_t i; size_t slen = s.length; @@ -2137,7 +2207,7 @@ string toUTF8(in dchar[] s) @trusted /* =================== Conversion to UTF16 ======================= */ -wchar[] toUTF16(ref wchar[2] buf, dchar c) nothrow @nogc @safe +wchar[] toUTF16(return ref wchar[2] buf, dchar c) nothrow @nogc @safe in { assert(isValidDchar(c)); @@ -2162,6 +2232,8 @@ body */ wstring toUTF16(in char[] s) @trusted { + import std.exception : assumeUnique; + wchar[] r; size_t slen = s.length; @@ -2195,6 +2267,8 @@ wstring toUTF16(in wchar[] s) @safe /// ditto pure wstring toUTF16(in dchar[] s) @trusted { + import std.exception : assumeUnique; + wchar[] r; size_t slen = s.length; @@ -2216,6 +2290,8 @@ pure wstring toUTF16(in dchar[] s) @trusted */ dstring toUTF32(in char[] s) @trusted { + import std.exception : assumeUnique; + dchar[] r; size_t slen = s.length; size_t j = 0; @@ -2237,6 +2313,8 @@ dstring toUTF32(in char[] s) @trusted /// ditto dstring toUTF32(in wchar[] s) @trusted { + import std.exception : assumeUnique; + dchar[] r; size_t slen = s.length; size_t j = 0; @@ -2397,6 +2475,7 @@ private P toUTFzImpl(P, S)(S str) @safe pure //C[] -> immutable(C)* else { + import std.array : uninitializedArray; auto copy = uninitializedArray!(Unqual!OutChar[])(str.length + 1); copy[0 .. $ - 1] = str[]; copy[$ - 1] = '\0'; @@ -2411,6 +2490,7 @@ private P toUTFzImpl(P, S)(S str) @safe pure !is(Unqual!(typeof(*P.init)) == Unqual!(ElementEncodingType!S))) //C1[], const(C1)[], or immutable(C1)[] -> C2*, const(C2)*, or immutable(C2)* { + import std.array : appender; auto retval = appender!(typeof(*P.init)[])(); foreach (dchar c; str) @@ -2422,6 +2502,10 @@ private P toUTFzImpl(P, S)(S str) @safe pure @safe pure unittest { + import std.conv : to; + import std.exception; + import std. string : format; + import core.exception : AssertError; import std.algorithm; assertCTFEable!( @@ -2519,6 +2603,7 @@ const(wchar)* toUTF16z(C)(const(C)[] str) @safe pure @safe pure unittest { + import std.conv : to; //toUTFz is already thoroughly tested, so this will just verify that //toUTF16z compiles properly for the various string types. foreach (S; TypeTuple!(string, wstring, dstring)) @@ -2530,6 +2615,7 @@ const(wchar)* toUTF16z(C)(const(C)[] str) @safe pure pure unittest { + import std.exception; debug(utf) printf("utf.toUTF.unittest\n"); assertCTFEable!( @@ -2576,6 +2662,7 @@ size_t count(C)(const(C)[] str) @trusted pure nothrow @nogc pure nothrow @nogc unittest { + import std.exception; assertCTFEable!( { assert(count("") == 0); @@ -2591,6 +2678,7 @@ version(unittest) { struct InputCU(C) { + import std.conv : to; @property bool empty() { return _str.empty; } @property C front() { return _str[0]; } void popFront() { _str = _str[1 .. $]; } @@ -2605,6 +2693,7 @@ version(unittest) struct BidirCU(C) { + import std.conv : to; @property bool empty() { return _str.empty; } @property C front() { return _str[0]; } void popFront() { _str = _str[1 .. $]; } @@ -2623,6 +2712,7 @@ version(unittest) struct RandomCU(C) { + import std.conv : to; @property bool empty() { return _str.empty; } @property C front() { return _str[0]; } void popFront() { _str = _str[1 .. $]; } @@ -2643,6 +2733,7 @@ version(unittest) class RefBidirCU(C) { + import std.conv : to; @property bool empty() { return _str.empty; } @property C front() { return _str[0]; } void popFront() { _str = _str[1 .. $]; } @@ -2661,6 +2752,7 @@ version(unittest) class RefRandomCU(C) { + import std.conv : to; @property bool empty() { return _str.empty; } @property C front() { return _str[0]; } void popFront() { _str = _str[1 .. $]; } @@ -2713,9 +2805,19 @@ auto byCodeUnit(R)(R r) if (isNarrowString!R) void popFront() { r = r[1 .. $]; } auto ref opIndex(size_t index) inout { return r[index]; } + @property auto ref back() inout + { + return r[$ - 1]; + } + + void popBack() + { + r = r[0 .. $-1]; + } + auto opSlice(size_t lower, size_t upper) { - return r[lower..upper]; + return ByCodeUnitImpl(r[lower..upper]); } @property size_t length() const @@ -2733,6 +2835,8 @@ auto byCodeUnit(R)(R r) if (isNarrowString!R) R r; } + static assert(isRandomAccessRange!ByCodeUnitImpl); + return ByCodeUnitImpl(r); } @@ -2746,54 +2850,73 @@ auto ref byCodeUnit(R)(R r) pure nothrow @nogc unittest { - { - char[5] s; - int i; - foreach (c; "hello".byCodeUnit().byCodeUnit()) + import std.range; { - s[i++] = c; + char[5] s; + int i; + foreach (c; "hello".byCodeUnit().byCodeUnit()) + { + s[i++] = c; + } + assert(s == "hello"); } - assert(s == "hello"); - } - { - wchar[5] s; - int i; - foreach (c; "hello"w.byCodeUnit().byCodeUnit()) { - s[i++] = c; + wchar[5] s; + int i; + foreach (c; "hello"w.byCodeUnit().byCodeUnit()) + { + s[i++] = c; + } + assert(s == "hello"w); } - assert(s == "hello"w); - } - { - dchar[5] s; - int i; - foreach (c; "hello"d.byCodeUnit().byCodeUnit()) { - s[i++] = c; + dchar[5] s; + int i; + foreach (c; "hello"d.byCodeUnit().byCodeUnit()) + { + s[i++] = c; + } + assert(s == "hello"d); + } + { + auto r = "hello".byCodeUnit(); + assert(r.length == 5); + assert(r[3] == 'l'); + assert(r[2..4][1] == 'l'); + } + { + char[5] buff = "hello"; + auto s = buff[].byCodeUnit(); + s.front = 'H'; + assert(s.front == 'H'); + s[1] = 'E'; + assert(s[1] == 'E'); + } + { + auto r = "hello".byCodeUnit().byCodeUnit(); + static assert(isForwardRange!(typeof(r))); + auto s = r.save; + r.popFront(); + assert(s.front == 'h'); + } + { + auto r = "hello".byCodeUnit(); + static assert(hasSlicing!(typeof(r))); + static assert(isBidirectionalRange!(typeof(r))); + auto ret = r.retro; + assert(ret.front == 'o'); + ret.popFront(); + assert(ret.front == 'l'); + } + { + auto r = "κόσμε"w.byCodeUnit(); + static assert(hasSlicing!(typeof(r))); + static assert(isBidirectionalRange!(typeof(r))); + auto ret = r.retro; + assert(ret.front == 'ε'); + ret.popFront(); + assert(ret.front == 'μ'); } - assert(s == "hello"d); - } - { - auto r = "hello".byCodeUnit(); - assert(r.length == 5); - assert(r[3] == 'l'); - assert(r[2..4][1] == 'l'); - } - { - char[5] buff = "hello"; - auto s = buff[].byCodeUnit(); - s.front = 'H'; - assert(s.front == 'H'); - s[1] = 'E'; - assert(s[1] == 'E'); - } - { - auto r = "hello".byCodeUnit().byCodeUnit(); - assert(isForwardRange!(typeof(r))); - auto s = r.save; - r.popFront(); - assert(s.front == 'h'); - } } /**************************** @@ -3337,8 +3460,6 @@ auto ref byDchar(R)(R r) static assert(0); } -import std.stdio; - pure nothrow @nogc unittest { { diff --git a/std/uuid.d b/std/uuid.d index b0039a5a090..3d60fa506e5 100644 --- a/std/uuid.d +++ b/std/uuid.d @@ -1,6 +1,14 @@ /** - + * A $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, UUID), or + * $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, Universally unique identifier), + * is intended to uniquely identify information in a distributed environment + * without significant central coordination. It can be + * used to tag objects with very short lifetimes, or to reliably identify very + * persistent objects across a network. + * +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Category) $(TH Functions) ) @@ -17,15 +25,9 @@ $(MYREF2 UUID.opEquals, opEquals) $(MYREF2 UUID.opCmp, opCmp) $(MYREF2 UUID.toHa $(TR $(TDNW UUID namespaces) $(TD $(MYREF dnsNamespace) $(MYREF urlNamespace) $(MYREF oidNamespace) $(MYREF x500Namespace) ) ) +) ) - * A $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, UUID), or - * $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, Universally unique identifier), - * is intended to uniquely identify information in a distributed environment - * without significant central coordination. It can be - * used to tag objects with very short lifetimes, or to reliably identify very - * persistent objects across a network. - * * UUIDs have many applications. Some examples follow: Databases may use UUIDs to identify * rows or records in order to ensure that they are unique across different * databases, or for publication/subscription services. Network messages may be @@ -83,12 +85,11 @@ $(MYREF oidNamespace) $(MYREF x500Namespace) ) * $(LINK http://en.wikipedia.org/wiki/Universally_unique_identifier) * * Copyright: Copyright Johannes Pfau 2011 - . - * License: Boost License 1.0 + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Johannes Pfau * Source: $(PHOBOSSRC std/_uuid.d) * * Macros: - * MYREF = $1  * MYREF2 = $1  * MYREF3 = $(D $1) */ @@ -99,14 +100,17 @@ $(MYREF oidNamespace) $(MYREF x500Namespace) ) */ module std.uuid; -import std.array, std.ascii; -import std.conv, std.digest.md, std.digest.sha, std.random, std.range, std.string, std.traits; +import std.range.primitives; +import std.traits; /** * */ public struct UUID { + import std.typetuple : allSatisfy; + import std.traits : isIntegral; + private: @safe pure nothrow char toChar(size_t i) const { @@ -235,12 +239,12 @@ public struct UUID * Construct a UUID struct from the 16 byte representation * of a UUID. */ - @safe pure nothrow this(ref in ubyte[16] uuidData) + @safe pure nothrow @nogc this(ref in ubyte[16] uuidData) { data = uuidData; } /// ditto - @safe pure nothrow this(in ubyte[16] uuidData) + @safe pure nothrow @nogc this(in ubyte[16] uuidData) { data = uuidData; } @@ -255,25 +259,28 @@ public struct UUID assert(ctfe.data == data); } -/+ - Not Working! DMD interprets the ubyte literals as ints, then complains the int can't - be converted to ubyte! - /** * Construct a UUID struct from the 16 byte representation * of a UUID. Variadic constructor to allow a simpler syntax, see examples. * You need to pass exactly 16 ubytes. - * - * Examples: - * ------------------------- - * auto tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); - * assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11, - * 12,13,14,15]); - * ------------------------- */ - @safe pure nothrow this()(ubyte[16] uuidData...) + @safe pure this(T...)(T uuidData) + if(uuidData.length == 16 && allSatisfy!(isIntegral, T)) { - data = uuidData; + import std.conv : to; + + foreach(idx, it; uuidData) + { + this.data[idx] = to!ubyte(it); + } + } + + /// + unittest + { + auto tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); + assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11, + 12,13,14,15]); } unittest @@ -292,8 +299,6 @@ public struct UUID assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1)))); } -++/ - /** * * Parse a UUID from its canonical string form. An UUID in its @@ -330,6 +335,7 @@ public struct UUID */ this(T)(in T[] uuid) if(isSomeChar!(Unqual!T)) { + import std.conv : to, parse; if(uuid.length < 36) { throw new UUIDParsingException(to!string(uuid), 0, @@ -390,6 +396,7 @@ public struct UUID { import std.exception; import std.typetuple; + import std.conv : to; foreach(S; TypeTuple!(char[], const(char)[], immutable(char)[], wchar[], const(wchar)[], immutable(wchar)[], @@ -447,7 +454,7 @@ public struct UUID * Returns true if and only if the UUID is equal * to {00000000-0000-0000-0000-000000000000} */ - @trusted pure nothrow @property bool empty() const + @trusted pure nothrow @nogc @property bool empty() const { if(__ctfe) return data == (ubyte[16]).init; @@ -510,7 +517,7 @@ public struct UUID * See_Also: * $(MYREF3 UUID.Variant, Variant) */ - @safe pure nothrow @property Variant variant() const + @safe pure nothrow @nogc @property Variant variant() const { //variant is stored in octet 7 //which is index 8, since indexes count backwards @@ -570,7 +577,7 @@ public struct UUID * See_Also: * $(MYREF3 UUID.Version, Version) */ - @safe pure nothrow @property Version uuidVersion() const + @safe pure nothrow @nogc @property Version uuidVersion() const { //version is stored in octet 9 //which is index 6, since indexes count backwards @@ -625,7 +632,7 @@ public struct UUID /** * Swap the data of this UUID with the data of rhs. */ - @safe pure nothrow void swap(ref UUID rhs) + @safe pure nothrow @nogc void swap(ref UUID rhs) { auto bck = data; data = rhs.data; @@ -647,36 +654,37 @@ public struct UUID /** * All of the standard numeric operators are defined for * the UUID struct. - * - * Examples: - * ------------------------- - * //compare UUIDs - * assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init); - * - * //UUIDs in associative arrays: - * int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1, - * UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2, - * UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3]; - * - * assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3); - * - * //UUIDS can be sorted: - * import std.algorithm; - * UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"), - * UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"), - * UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")]; - * sort(ids); - * ------------------------- */ - @safe pure nothrow bool opEquals(in UUID s) const + @safe pure nothrow @nogc bool opEquals(in UUID s) const { return s.data == this.data; } + /// + pure unittest + { + //compare UUIDs + assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init); + + //UUIDs in associative arrays: + int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1, + UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2, + UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3]; + + assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3); + + //UUIDS can be sorted: + import std.algorithm; + UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"), + UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"), + UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")]; + sort(ids); + } + /** * ditto */ - @safe pure nothrow bool opEquals(ref in UUID s) const + @safe pure nothrow @nogc bool opEquals(ref in UUID s) const { return s.data == this.data; } @@ -684,23 +692,25 @@ public struct UUID /** * ditto */ - @safe pure nothrow int opCmp(in UUID s) const + @safe pure nothrow @nogc int opCmp(in UUID s) const { + import std.algorithm : cmp; return cmp(this.data[], s.data[]); } /** * ditto */ - @safe pure nothrow int opCmp(ref in UUID s) const + @safe pure nothrow @nogc int opCmp(ref in UUID s) const { + import std.algorithm : cmp; return cmp(this.data[], s.data[]); } /** * ditto */ - @safe pure nothrow size_t toHash() const + @safe pure nothrow @nogc size_t toHash() const { size_t seed = 0; foreach(entry; this.data) @@ -772,11 +782,7 @@ public struct UUID ///ditto @safe pure nothrow string toString() const { - //@@@BUG@@@ workaround for bugzilla 5700 - try - return _toString().idup; - catch(Exception) - assert(0, "It should be impossible for idup to throw."); + return _toString().idup; } /// @@ -820,16 +826,6 @@ public struct UUID * CTFE: * CTFE is not supported. * - * Examples: - * --------------------------------------- - * //Use default UUID.init namespace - * auto simpleID = md5UUID("test.uuid.any.string"); - * - * //use a name-based id as namespace - * auto namespace = md5UUID("my.app"); - * auto id = md5UUID("some-description", namespace); - * --------------------------------------- - * * Note: * RFC 4122 isn't very clear on how UUIDs should be generated from names. * It is possible that different implementations return different UUIDs @@ -847,7 +843,7 @@ public struct UUID * for strings and wstrings. It's always possible to pass wstrings and dstrings * by using the ubyte[] function overload (but be aware of endianness issues!). */ -@safe pure UUID md5UUID(const(char[]) name, const UUID namespace = UUID.init) +@safe pure nothrow @nogc UUID md5UUID(const(char[]) name, const UUID namespace = UUID.init) { return md5UUID(cast(const(ubyte[]))name, namespace); } @@ -855,8 +851,10 @@ public struct UUID /** * ditto */ -@safe pure UUID md5UUID(const(ubyte[]) data, const UUID namespace = UUID.init) +@safe pure nothrow @nogc UUID md5UUID(const(ubyte[]) data, const UUID namespace = UUID.init) { + import std.digest.md : MD5; + MD5 hash; hash.start(); @@ -884,6 +882,17 @@ public struct UUID return u; } +/// +unittest +{ + //Use default UUID.init namespace + auto simpleID = md5UUID("test.uuid.any.string"); + + //use a name-based id as namespace + auto namespace = md5UUID("my.app"); + auto id = md5UUID("some-description", namespace); +} + @safe pure unittest { auto simpleID = md5UUID("test.uuid.any.string"); @@ -930,16 +939,6 @@ public struct UUID * CTFE: * CTFE is not supported. * - * Examples: - * --------------------------------------- - * //Use default UUID.init namespace - * auto simpleID = sha1UUID("test.uuid.any.string"); - * - * //use a name-based id as namespace - * auto namespace = sha1UUID("my.app"); - * auto id = sha1UUID("some-description", namespace); - * --------------------------------------- - * * Note: * RFC 4122 isn't very clear on how UUIDs should be generated from names. * It is possible that different implementations return different UUIDs @@ -957,7 +956,7 @@ public struct UUID * for strings and wstrings. It's always possible to pass wstrings and dstrings * by using the ubyte[] function overload (but be aware of endianness issues!). */ -@safe pure UUID sha1UUID(in char[] name, const UUID namespace = UUID.init) +@safe pure nothrow @nogc UUID sha1UUID(in char[] name, const UUID namespace = UUID.init) { return sha1UUID(cast(const(ubyte[]))name, namespace); } @@ -965,8 +964,10 @@ public struct UUID /** * ditto */ -@safe pure UUID sha1UUID(in ubyte[] data, const UUID namespace = UUID.init) +@safe pure nothrow @nogc UUID sha1UUID(in ubyte[] data, const UUID namespace = UUID.init) { + import std.digest.sha : SHA1; + SHA1 sha; sha.start(); @@ -995,6 +996,17 @@ public struct UUID return u; } +/// +unittest +{ + //Use default UUID.init namespace + auto simpleID = sha1UUID("test.uuid.any.string"); + + //use a name-based id as namespace + auto namespace = sha1UUID("my.app"); + auto id = sha1UUID("some-description", namespace); +} + @safe pure unittest { auto simpleID = sha1UUID("test.uuid.any.string"); @@ -1031,40 +1043,29 @@ public struct UUID * CTFE: * This function is not supported at compile time. * - * Examples: - * ------------------------------------------ - * //simple call - * auto uuid = randomUUID(); - * - * //provide a custom RNG. Must be seeded manually. - * Xorshift192 gen; - * - * gen.seed(unpredictableSeed); - * auto uuid3 = randomUUID(gen); - * ------------------------------------------ */ @trusted UUID randomUUID() { + import std.random : rndGen, Mt19937; return randomUUID(rndGen); } -/* - * Original boost.uuid used Mt19937, we don't want - * to use anything worse than that. If Random is changed - * to something else, this assert and the randomUUID function - * have to be updated. - */ -static assert(is(typeof(rndGen) == Mt19937)); /** * ditto */ -@trusted UUID randomUUID(RNG)(ref RNG randomGen) if(isUniformRNG!(RNG) && - isIntegral!(typeof(RNG.front))) +/** + * Params: + * randomGen = uniform RNG + * See_Also: $(XREF random, isUniformRNG) + */ +@trusted UUID randomUUID(RNG)(ref RNG randomGen) if(isIntegral!(typeof(RNG.front))) { + import std.random : isUniformRNG; + static assert(isUniformRNG!RNG, "randomGen must be an uniform RNG"); enum size_t elemSize = typeof(RNG.front).sizeof; static assert(elemSize <= 16); UUID u; - foreach(size_t i; iota(cast(size_t)0, cast(size_t)16, elemSize)) + for(size_t i; i < 16; i += elemSize) { randomGen.popFront(); immutable randomValue = randomGen.front; @@ -1084,9 +1085,36 @@ static assert(is(typeof(rndGen) == Mt19937)); return u; } +/// +unittest +{ + import std.random : Xorshift192, unpredictableSeed; + + //simple call + auto uuid = randomUUID(); + + //provide a custom RNG. Must be seeded manually. + Xorshift192 gen; + + gen.seed(unpredictableSeed); + auto uuid3 = randomUUID(gen); +} + +/* + * Original boost.uuid used Mt19937, we don't want + * to use anything worse than that. If Random is changed + * to something else, this assert and the randomUUID function + * have to be updated. + */ +unittest +{ + import std.random : rndGen, Mt19937; + static assert(is(typeof(rndGen) == Mt19937)); +} + unittest { - import std.random; + import std.random : Xorshift192, unpredictableSeed; //simple call auto uuid = randomUUID(); @@ -1129,25 +1157,6 @@ unittest * This function is supported in CTFE code. Note that error messages * caused by a malformed UUID parsed at compile time can be cryptic, * but errors are detected and reported at compile time. - * - * Examples: - * ------------------------- - * auto id = parseUUID("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46"); - * //no dashes - * id = parseUUID("8ab3060e2cba4f23b74cb52db3bdfb46"); - * //dashes at different positions - * id = parseUUID("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46"); - * //leading / trailing characters - * id = parseUUID("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}"); - * //unicode - * id = parseUUID("ü8ab3060e2cba4f23b74cb52db3bdfb46ü"); - * //multiple trailing/leading characters - * id = parseUUID("///8ab3060e2cba4f23b74cb52db3bdfb46||"); - * - * //Can also be used in CTFE, for example as UUID literals: - * enum ctfeID = parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); - * //here parsing is done at compile time, no runtime overhead! - * ------------------------- */ UUID parseUUID(T)(T uuidString) if(isSomeString!T) { @@ -1158,6 +1167,9 @@ UUID parseUUID(T)(T uuidString) if(isSomeString!T) UUID parseUUID(Range)(ref Range uuidRange) if(isInputRange!Range && is(Unqual!(ElementType!Range) == dchar)) { + import std.conv : ConvException, parse; + import std.ascii : isHexDigit; + static if(isForwardRange!Range) auto errorCopy = uuidRange.save; @@ -1166,6 +1178,7 @@ UUID parseUUID(Range)(ref Range uuidRange) if(isInputRange!Range { static if(isForwardRange!Range) { + import std.conv : to; static if(isInfinite!Range) { throw new UUIDParsingException(to!string(take(errorCopy, pos)), pos, reason, message, @@ -1185,6 +1198,7 @@ UUID parseUUID(Range)(ref Range uuidRange) if(isInputRange!Range static if(hasLength!Range) { + import std.conv : to; if(uuidRange.length < 32) { throw new UUIDParsingException(to!string(uuidRange), 0, UUIDParsingException.Reason.tooLittle, @@ -1292,10 +1306,31 @@ UUID parseUUID(Range)(ref Range uuidRange) if(isInputRange!Range return result; } +/// +unittest +{ + auto id = parseUUID("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46"); + //no dashes + id = parseUUID("8ab3060e2cba4f23b74cb52db3bdfb46"); + //dashes at different positions + id = parseUUID("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46"); + //leading / trailing characters + id = parseUUID("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}"); + //unicode + id = parseUUID("ü8ab3060e2cba4f23b74cb52db3bdfb46ü"); + //multiple trailing/leading characters + id = parseUUID("///8ab3060e2cba4f23b74cb52db3bdfb46||"); + + //Can also be used in CTFE, for example as UUID literals: + enum ctfeID = parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + //here parsing is done at compile time, no runtime overhead! +} + @safe pure unittest { import std.exception; import std.typetuple; + import std.conv : to; struct TestRange(bool forward) { @@ -1459,29 +1494,6 @@ enum x500Namespace = UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8"); /** * Regex string to extract UUIDs from text. - * - * Examples: - * ------------------- - * import std.algorithm; - * import std.regex; - * - * string test = "Lorem ipsum dolor sit amet, consetetur " - * "6ba7b814-9dad-11d1-80b4-00c04fd430c8 sadipscing \n" - * "elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore \r\n" - * "magna aliquyam erat, sed diam voluptua. " - * "8ab3060e-2cba-4f23-b74c-b52db3bdfb46 At vero eos et accusam et " - * "justo duo dolores et ea rebum."; - * - * auto r = regex(uuidRegex, "g"); - * - * UUID[] found; - * foreach(c; match(test, r)) - * { - * found ~= UUID(c.hit); - * } - * - * writeln(found); - * ------------------- */ enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~ "-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"; @@ -1538,6 +1550,8 @@ public class UUIDParsingException : Exception private this(string input, size_t pos, Reason why = Reason.unknown, string msg = "", Throwable next = null, string file = __FILE__, size_t line = __LINE__) pure @trusted { + import std.array : replace; + import std.format : format; this.input = input; this.position = pos; this.reason = why; @@ -1548,6 +1562,7 @@ public class UUIDParsingException : Exception } } +/// unittest { auto ex = new UUIDParsingException("foo", 10, UUIDParsingException.Reason.tooMuch); diff --git a/std/variant.d b/std/variant.d index 9b87bc53a79..f0dbaef7d29 100644 --- a/std/variant.d +++ b/std/variant.d @@ -54,7 +54,7 @@ * towards the garbage collector. * * Copyright: Copyright Andrei Alexandrescu 2007 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB erdani.org, Andrei Alexandrescu) * Source: $(PHOBOSSRC std/_variant.d) */ @@ -605,8 +605,6 @@ public: static assert(allowed!(T), "Cannot store a " ~ T.stringof ~ " in a " ~ VariantN.stringof ~ ". Valid types are " ~ AllowedTypes.stringof); - // Assignment should destruct previous value - fptr(OpID.destruct, &store, null); static if (is(T : VariantN)) { @@ -620,6 +618,12 @@ public: } else { + static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes)) + { + // Assignment should destruct previous value + fptr(OpID.destruct, &store, null); + } + static if (T.sizeof <= size) { // If T is a class we're only copying the reference, so it @@ -670,39 +674,31 @@ public: /** Returns true if and only if the $(D_PARAM VariantN) object * holds a valid value (has been initialized with, or assigned * from, a valid value). - * Example: - * ---- - * Variant a; - * assert(!a.hasValue); - * Variant b; - * a = b; - * assert(!a.hasValue); // still no value - * a = 5; - * assert(a.hasValue); - * ---- */ - @property bool hasValue() const pure nothrow { // @@@BUG@@@ in compiler, the cast shouldn't be needed return cast(typeof(&handler!(void))) fptr != &handler!(void); } + /// + unittest + { + Variant a; + assert(!a.hasValue); + Variant b; + a = b; + assert(!a.hasValue); // still no value + a = 5; + assert(a.hasValue); + } + /** * If the $(D_PARAM VariantN) object holds a value of the * $(I exact) type $(D_PARAM T), returns a pointer to that * value. Otherwise, returns $(D_PARAM null). In cases * where $(D_PARAM T) is statically disallowed, $(D_PARAM * peek) will not compile. - * - * Example: - * ---- - * Variant a = 5; - * auto b = a.peek!(int); - * assert(b !is null); - * *b = 6; - * assert(a == 6); - * ---- */ @property inout(T)* peek(T)() inout { @@ -717,6 +713,16 @@ public: return *cast(inout T**)&store; } + /// + unittest + { + Variant a = 5; + auto b = a.peek!(int); + assert(b !is null); + *b = 6; + assert(a == 6); + } + /** * Returns the $(D_PARAM typeid) of the currently held value. */ @@ -743,32 +749,6 @@ public: return fptr(OpID.testConversion, null, &info) == 0; } - // private T[] testing123(T)(T*); - - // /** - // * A workaround for the fact that functions cannot return - // * statically-sized arrays by value. Essentially $(D_PARAM - // * DecayStaticToDynamicArray!(T[N])) is an alias for $(D_PARAM - // * T[]) and $(D_PARAM DecayStaticToDynamicArray!(T)) is an alias - // * for $(D_PARAM T). - // */ - - // template DecayStaticToDynamicArray(T) - // { - // static if (isStaticArray!(T)) - // { - // alias DecayStaticToDynamicArray = typeof(testing123(&T[0])); - // } - // else - // { - // alias DecayStaticToDynamicArray = T; - // } - // } - - // static assert(is(DecayStaticToDynamicArray!(immutable(char)[21]) == - // immutable(char)[]), - // DecayStaticToDynamicArray!(immutable(char)[21]).stringof); - /** * Returns the value stored in the $(D_PARAM VariantN) object, * implicitly converted to the requested type $(D_PARAM T), in @@ -779,13 +759,22 @@ public: @property T get(T)() if (!is(T == const)) { - union Buf + auto p = *cast(T**) &store; + + /* handler(OpID.get, ) expects the TypeInfo for T in the same buffer it + * writes the result to afterwards. Because T might have a non-trivial + * destructor, postblit or invariant, we cannot use a union. + */ + struct Buf { - TypeInfo info; T result; + // Make sure Buf.sizeof is big enough to store a TypeInfo in + void[T.sizeof < TypeInfo.sizeof ? TypeInfo.sizeof - T.sizeof : 0] init = void; } - auto p = *cast(T**) &store; - Buf buf = { typeid(T) }; + TypeInfo info = typeid(T); + Buf buf; + memcpy(&buf, &info, info.sizeof); + if (fptr(OpID.get, &store, &buf)) { throw new VariantException(type, typeid(T)); @@ -795,16 +784,26 @@ public: @property T get(T)() const if (is(T == const)) { - union Buf + auto p = *cast(T**) &store; + + /* handler(OpID.get, ) expects the TypeInfo for T in the same buffer it + * writes the result to afterwards. Because T might have a non-trivial + * destructor, postblit or invariant, we cannot use a union. + */ + struct Buf { - TypeInfo info; static if (is(T == shared)) shared(Unqual!T) result; else Unqual!T result; + + // Make sure Buf.sizeof is big enough to store a TypeInfo in + void[T.sizeof < TypeInfo.sizeof ? TypeInfo.sizeof - T.sizeof : 0] init = void; } - auto p = *cast(T**) &store; - Buf buf = { typeid(T) }; + TypeInfo info = typeid(T); + Buf buf; + memcpy(&buf, &info, info.sizeof); + if (fptr(OpID.get, cast(ubyte[size]*) &store, &buf)) { throw new VariantException(type, typeid(T)); @@ -935,7 +934,7 @@ public: { string tryUseType(string tp) { - import std.string : format; + import std.format : format; return q{ static if (allowed!%1$s && T.allowed!%1$s) if (convertsTo!%1$s && other.convertsTo!%1$s) @@ -1117,28 +1116,6 @@ public: * Array and associative array operations. If a $(D_PARAM * VariantN) contains an (associative) array, it can be indexed * into. Otherwise, an exception is thrown. - * - * Example: - * ---- - * auto a = Variant(new int[10]); - * a[5] = 42; - * assert(a[5] == 42); - * int[int] hash = [ 42:24 ]; - * a = hash; - * assert(a[42] == 24); - * ---- - * - * Caveat: - * - * Due to limitations in current language, read-modify-write - * operations $(D_PARAM op=) will not work properly: - * - * ---- - * Variant a = new int[10]; - * a[5] = 42; - * a[5] += 8; - * assert(a[5] == 50); // fails, a[5] is still 42 - * ---- */ Variant opIndex(K)(K i) { @@ -1147,6 +1124,29 @@ public: return result; } + /// + unittest + { + auto a = Variant(new int[10]); + a[5] = 42; + assert(a[5] == 42); + int[int] hash = [ 42:24 ]; + a = hash; + assert(a[42] == 24); + } + + /** Caveat: + Due to limitations in current language, read-modify-write + operations $(D_PARAM op=) will not work properly: + */ + unittest + { + Variant a = new int[10]; + a[5] = 42; + a[5] += 8; + //assert(a[5] == 50); // will fail, a[5] is still 42 + } + unittest { int[int] hash = [ 42:24 ]; @@ -1299,6 +1299,14 @@ unittest assert(aa["b"] == 3); } +pure nothrow @nogc +unittest +{ + Algebraic!(int, double) a; + a = 100; + a = 1.0; +} + /** * Algebraic data type restricted to a closed set of possible @@ -1317,16 +1325,6 @@ unittest * Currently, $(D_PARAM Algebraic) does not allow recursive data * types. They will be allowed in a future iteration of the * implementation. - * - * Example: - * ---- - * auto v = Algebraic!(int, double, string)(5); - * assert(v.peek!(int)); - * v = 3.14; - * assert(v.peek!(double)); - * // auto x = v.peek!(long); // won't compile, type long not allowed - * // v = '1'; // won't compile, type char not allowed - * ---- */ template Algebraic(T...) @@ -1334,6 +1332,17 @@ template Algebraic(T...) alias Algebraic = VariantN!(maxSize!(T), T); } +/// +unittest +{ + auto v = Algebraic!(int, double, string)(5); + assert(v.peek!(int)); + v = 3.14; + assert(v.peek!(double)); + // auto x = v.peek!(long); // won't compile, type long not allowed + // v = '1'; // won't compile, type char not allowed +} + /** $(D_PARAM Variant) is an alias for $(D_PARAM VariantN) instantiated with the largest of $(D_PARAM creal), $(D_PARAM char[]), and $(D_PARAM @@ -1348,37 +1357,11 @@ alias Variant = VariantN!(maxSize!(creal, char[], void delegate())); /** * Returns an array of variants constructed from $(D_PARAM args). - * Example: - * ---- - * auto a = variantArray(1, 3.14, "Hi!"); - * assert(a[1] == 3.14); - * auto b = Variant(a); // variant array as variant - * assert(b[1] == 3.14); - * ---- - * - * Code that needs functionality similar to the $(D_PARAM boxArray) - * function in the $(D_PARAM std.boxer) module can achieve it like this: - * - * ---- - * // old - * Box[] fun(...) - * { - * ... - * return boxArray(_arguments, _argptr); - * } - * // new - * Variant[] fun(T...)(T args) - * { - * ... - * return variantArray(args); - * } - * ---- * * This is by design. During construction the $(D_PARAM Variant) needs * static type information about the type being held, so as to store a * pointer to function for fast retrieval. */ - Variant[] variantArray(T...)(T args) { Variant[] result; @@ -1389,6 +1372,37 @@ Variant[] variantArray(T...)(T args) return result; } +/// +unittest +{ + auto a = variantArray(1, 3.14, "Hi!"); + assert(a[1] == 3.14); + auto b = Variant(a); // variant array as variant + assert(b[1] == 3.14); +} + +/** Code that needs functionality similar to the $(D_PARAM boxArray) +function in the $(D_PARAM std.boxer) module can achieve it like this: +*/ +unittest +{ + /* old + Box[] fun(...) + { + // ... + return boxArray(_arguments, _argptr); + } + */ + // new + Variant[] fun(T...)(T args) + { + // ... + return variantArray(args); + } +} + +/** + /** * Thrown in three cases: * @@ -1938,26 +1952,6 @@ unittest * * Duplicate overloads matching the same type in one of the visitors are disallowed. * - * Example: - * ----------------------- - * Algebraic!(int, string) variant; - * - * variant = 10; - * assert(variant.visit!((string s) => cast(int)s.length, - * (int i) => i)() - * == 10); - * variant = "string"; - * assert(variant.visit!((int i) => return i, - * (string s) => cast(int)s.length)() - * == 6); - * - * // Error function usage - * Algebraic!(int, string) emptyVar; - * assert(variant.visit!((string s) => cast(int)s.length, - * (int i) => i, - * () => -1)() - * == -1); - * ---------------------- * Returns: The return type of visit is deduced from the visiting functions and must be * the same across all overloads. * Throws: If no parameter-less, error function is specified: @@ -1973,6 +1967,28 @@ template visit(Handler ...) } } +/// +unittest +{ + Algebraic!(int, string) variant; + + variant = 10; + assert(variant.visit!((string s) => cast(int)s.length, + (int i) => i)() + == 10); + variant = "string"; + assert(variant.visit!((int i) => i, + (string s) => cast(int)s.length)() + == 6); + + // Error function usage + Algebraic!(int, string) emptyVar; + auto rslt = emptyVar.visit!((string s) => cast(int)s.length, + (int i) => i, + () => -1)(); + assert(rslt == -1); +} + unittest { Algebraic!(size_t, string) variant; @@ -2040,22 +2056,6 @@ unittest * either $(D_PARAM variant) doesn't hold a value or holds a type * which isn't handled by the visiting functions. * - * Example: - * ----------------------- - * Algebraic!(int, string) variant; - * - * variant = 10; - * auto which = -1; - * variant.tryVisit!((int i) { which = 0; })(); - * assert(which = 0); - * - * // Error function usage - * variant = "test"; - * variant.tryVisit!((int i) { which = 0; }, - * () { which = -100; })(); - * assert(which == -100); - * ---------------------- - * * Returns: The return type of tryVisit is deduced from the visiting functions and must be * the same across all overloads. * Throws: If no parameter-less, error function is specified: $(D_PARAM VariantException) if @@ -2073,6 +2073,23 @@ template tryVisit(Handler ...) } } +/// +unittest +{ + Algebraic!(int, string) variant; + + variant = 10; + auto which = -1; + variant.tryVisit!((int i) { which = 0; })(); + assert(which == 0); + + // Error function usage + variant = "test"; + variant.tryVisit!((int i) { which = 0; }, + () { which = -100; })(); + assert(which == -100); +} + unittest { Algebraic!(int, string) variant; @@ -2495,6 +2512,17 @@ unittest auto a = appender!(T[]); } +unittest +{ + // Bugzilla 13871 + alias A = Algebraic!(int, typeof(null)); + static struct B { A value; } + alias C = std.variant.Algebraic!B; + + C var; + var = C(B()); +} + unittest { // Make sure Variant can handle types with opDispatch but no length field. diff --git a/std/windows/charset.d b/std/windows/charset.d index da644fa4c08..9d2cf755f5b 100644 --- a/std/windows/charset.d +++ b/std/windows/charset.d @@ -7,7 +7,7 @@ * WIKI = Phobos/StdWindowsCharset * * Copyright: Copyright Digital Mars 2005 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) */ /* Copyright Digital Mars 2005 - 2009. @@ -19,7 +19,7 @@ module std.windows.charset; version (Windows): private import std.conv; -private import std.c.windows.windows; +private import core.sys.windows.windows; private import std.windows.syserror; private import std.utf; private import std.string; diff --git a/std/windows/iunknown.d b/std/windows/iunknown.d index 7305115a532..d206d7052f6 100644 --- a/std/windows/iunknown.d +++ b/std/windows/iunknown.d @@ -1,7 +1,7 @@ // Written in the D programming language. +/// Please import core.sys.windows.com instead. This module will be deprecated in DMD 2.068. module std.windows.iunknown; -version (Windows): // Replaced by: -public import std.c.windows.com; +public import core.sys.windows.com; diff --git a/std/windows/registry.d b/std/windows/registry.d index 85b01c7e807..4823f3a7561 100644 --- a/std/windows/registry.d +++ b/std/windows/registry.d @@ -4,7 +4,7 @@ Copyright: Copyright 2003-2004 by Matthew Wilson and Synesis Software Written by Matthew Wilson - License: + License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Author: Matthew Wilson, Kenji Hara @@ -41,7 +41,7 @@ version (Windows): import std.array; import std.system : Endian, endian; import std.exception; -import std.c.windows.windows; +import core.sys.windows.windows; import std.windows.syserror; import std.conv; import std.utf : toUTF8, toUTF16; @@ -1279,20 +1279,6 @@ public: return value; } - //Explicitly undocumented. It will be removed in July 2014. - deprecated("Please use value_DWORD instead.") - uint value_DWORD_LITTLEENDIAN() - { - return value_DWORD; - } - - //Explicitly undocumented. It will be removed in July 2014. - deprecated("Please use value_DWORD instead.") - uint value_DWORD_BIGENDIAN() - { - return value_DWORD; - } - /** Obtains the value as a 64-bit unsigned integer, ordered correctly according to the current architecture. diff --git a/std/windows/syserror.d b/std/windows/syserror.d index 56df1290e7c..60c610ddff3 100644 --- a/std/windows/syserror.d +++ b/std/windows/syserror.d @@ -4,7 +4,7 @@ * Convert Win32 error code to string. * * Copyright: Copyright Digital Mars 2006 - 2013. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * Credits: Based on code written by Regan Heath * @@ -14,68 +14,153 @@ * http://www.boost.org/LICENSE_1_0.txt) */ module std.windows.syserror; +import std.traits : isSomeString; + +version (StdDdoc) +{ + private + { + alias DWORD = uint; + enum LANG_NEUTRAL = 0, SUBLANG_DEFAULT = 1; + } + + /// Query the text for a Windows error code (as returned by $(LINK2 + /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx, + /// $(D GetLastError))) as a D string. + string sysErrorString( + DWORD errCode, + // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language + int langId = LANG_NEUTRAL, + int subLangId = SUBLANG_DEFAULT) @trusted; + + /********************* + * Thrown if errors that set $(LINK2 + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx, + * $(D GetLastError)) occur. + */ + class WindowsException : Exception + { + private alias DWORD = int; + final @property DWORD code(); /// $(D GetLastError)'s return value. + @disable this(int dummy); + } + + /++ + If $(D !!value) is true, $(D value) is returned. Otherwise, + $(D new WindowsException(GetLastError(), msg)) is thrown. + $(D WindowsException) assumes that the last operation set + $(D GetLastError()) appropriately. + + Example: + -------------------- + wenforce(DeleteFileA("junk.tmp"), "DeleteFile failed"); + -------------------- + +/ + T wenforce(T, S)(T value, lazy S msg = null, + string file = __FILE__, size_t line = __LINE__) @safe + if (isSomeString!S); +} +else: + version (Windows): import std.windows.charset; +import std.array : appender; +import std.conv : to; +import std.format : formattedWrite; import core.sys.windows.windows; -// MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language string sysErrorString( - uint errCode, + DWORD errCode, + // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language int langId = LANG_NEUTRAL, int subLangId = SUBLANG_DEFAULT) @trusted { - wchar* pWideMessage; + auto buf = appender!string(); + + if (!putSysError(errCode, buf, MAKELANGID(langId, subLangId))) + { + throw new Exception( + "failed getting error string for WinAPI error code: " ~ + sysErrorString(GetLastError())); + } - DWORD length = FormatMessageW( + return buf.data; +} + +bool putSysError(Writer)(DWORD code, Writer w, /*WORD*/int langId = 0) +{ + wchar *lpMsgBuf = null; + auto res = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, - errCode, - MAKELANGID(langId, subLangId), - cast(LPWSTR)&pWideMessage, + code, + langId, + cast(LPWSTR)&lpMsgBuf, 0, null); + scope(exit) if (lpMsgBuf) LocalFree(lpMsgBuf); - if(length == 0) + if (lpMsgBuf) { - throw new Exception( - "failed getting error string for WinAPI error code: " ~ - sysErrorString(GetLastError())); + import std.string : strip; + w.put(lpMsgBuf[0..res].strip()); + return true; } + else + return false; +} - scope(exit) LocalFree(cast(HLOCAL)pWideMessage); - /* Remove \r\n from error string */ - if (length >= 2) - length -= 2; +class WindowsException : Exception +{ + import core.sys.windows.windows; + + final @property DWORD code() { return _code; } /// $(D GetLastError)'s return value. + private DWORD _code; - static int wideToNarrow(wchar[] wide, char[] narrow) nothrow + this(DWORD code, string str=null, string file = null, size_t line = 0) @trusted { - return WideCharToMultiByte( - CP_UTF8, - 0, // No WC_COMPOSITECHECK, as system error messages are precomposed - wide.ptr, - cast(int)wide.length, - narrow.ptr, - cast(int)narrow.length, - null, - null); - } + _code = code; + + auto buf = appender!string(); - auto wideMessage = pWideMessage[0 .. length]; + if (str != null) + { + buf.put(str); + buf.put(": "); + } - int requiredCodeUnits = wideToNarrow(wideMessage, null); + auto success = putSysError(code, buf); + formattedWrite(buf, success ? " (error %d)" : "Error %d", code); + + super(buf.data, file, line); + } +} - // If FormatMessage with FORMAT_MESSAGE_FROM_SYSTEM succeeds, - // there's no reason for the returned UTF-16 to be invalid. - assert(requiredCodeUnits > 0); - auto message = new char[requiredCodeUnits]; - auto writtenLength = wideToNarrow(wideMessage, message); +T wenforce(T, S)(T value, lazy S msg = null, + string file = __FILE__, size_t line = __LINE__) if (isSomeString!S) +{ + if (!value) + throw new WindowsException(GetLastError(), to!string(msg), file, line); + return value; +} - assert(writtenLength > 0); // Ditto +version(Windows) +unittest +{ + import std.exception; + import std.string; + import std.algorithm : startsWith, endsWith; - return cast(immutable)message[0 .. writtenLength]; + auto e = collectException!WindowsException( + DeleteFileA("unexisting.txt").wenforce("DeleteFile") + ); + assert(e.code == ERROR_FILE_NOT_FOUND); + assert(e.msg.startsWith("DeleteFile: ")); + // can't test the entire message, as it depends on Windows locale + assert(e.msg.endsWith(" (error 2)")); } diff --git a/std/xml.d b/std/xml.d index e7a26c1977c..474e81b82c9 100644 --- a/std/xml.d +++ b/std/xml.d @@ -116,7 +116,7 @@ Macros: WIKI=Phobos/StdXml Copyright: Copyright Janice Caron 2008 - 2009. -License: Boost License 1.0. +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: Janice Caron Source: $(PHOBOSSRC std/_xml.d) */ @@ -871,7 +871,7 @@ class Element : Item * You should rarely need to call this function. It exists so that Elements * can be used as associative array keys. */ - override const size_t toHash() + override size_t toHash() const { size_t hash = tag.toHash(); foreach(item;items) hash += item.toHash(); @@ -1271,14 +1271,14 @@ class Comment : Item * You should rarely need to call this function. It exists so that Comments * can be used as associative array keys. */ - override const size_t toHash() { return hash(content); } + override size_t toHash() const { return hash(content); } /** * Returns a string representation of this comment */ - override const string toString() { return ""; } + override string toString() const { return ""; } - override @property const bool isEmptyXML() { return false; } /// Returns false always + override @property bool isEmptyXML() const { return false; } /// Returns false always } /** @@ -1350,14 +1350,14 @@ class CData : Item * You should rarely need to call this function. It exists so that CDatas * can be used as associative array keys. */ - override const size_t toHash() { return hash(content); } + override size_t toHash() const { return hash(content); } /** * Returns a string representation of this CData section */ - override const string toString() { return cdata ~ content ~ "]]>"; } + override string toString() const { return cdata ~ content ~ "]]>"; } - override @property const bool isEmptyXML() { return false; } /// Returns false always + override @property bool isEmptyXML() const { return false; } /// Returns false always } /** @@ -1427,17 +1427,17 @@ class Text : Item * You should rarely need to call this function. It exists so that Texts * can be used as associative array keys. */ - override const size_t toHash() { return hash(content); } + override size_t toHash() const { return hash(content); } /** * Returns a string representation of this Text section */ - override const string toString() { return content; } + override string toString() const { return content; } /** * Returns true if the content is the empty string */ - override @property const bool isEmptyXML() { return content.length == 0; } + override @property bool isEmptyXML() const { return content.length == 0; } } /** @@ -1509,14 +1509,14 @@ class XMLInstruction : Item * You should rarely need to call this function. It exists so that * XmlInstructions can be used as associative array keys. */ - override const size_t toHash() { return hash(content); } + override size_t toHash() const { return hash(content); } /** * Returns a string representation of this XmlInstruction */ - override const string toString() { return ""; } + override string toString() const { return ""; } - override @property const bool isEmptyXML() { return false; } /// Returns false always + override @property bool isEmptyXML() const { return false; } /// Returns false always } /** @@ -1588,14 +1588,14 @@ class ProcessingInstruction : Item * You should rarely need to call this function. It exists so that * ProcessingInstructions can be used as associative array keys. */ - override const size_t toHash() { return hash(content); } + override size_t toHash() const { return hash(content); } /** * Returns a string representation of this ProcessingInstruction */ - override const string toString() { return ""; } + override string toString() const { return ""; } - override @property const bool isEmptyXML() { return false; } /// Returns false always + override @property bool isEmptyXML() const { return false; } /// Returns false always } /** @@ -1610,10 +1610,10 @@ abstract class Item abstract override int opCmp(Object o); /// Returns the hash of this item - abstract override const size_t toHash(); + abstract override size_t toHash() const; /// Returns a string representation of this item - abstract override const string toString(); + abstract override string toString() const; /** * Returns an indented string representation of this item @@ -1621,14 +1621,14 @@ abstract class Item * Params: * indent = number of spaces by which to indent child elements */ - const string[] pretty(uint indent) + string[] pretty(uint indent) const { string s = strip(toString()); return s.length == 0 ? [] : [ s ]; } /// Returns true if the item represents empty XML text - abstract @property const bool isEmptyXML(); + abstract @property bool isEmptyXML() const; } /** @@ -1734,7 +1734,7 @@ class ElementParser * The Tag at the start of the element being parsed. You can read this to * determine the tag's name and attributes. */ - const @property const(Tag) tag() { return tag_; } + @property const(Tag) tag() const { return tag_; } /** * Register a handler which will be called whenever a start tag is @@ -2075,7 +2075,7 @@ class ElementParser /** * Returns that part of the element which has already been parsed */ - override const string toString() + override string toString() const { assert(elementStart.length >= s.length); return elementStart[0 .. elementStart.length - s.length]; @@ -2810,7 +2810,7 @@ class CheckException : XMLException if (err !is null) err.complete(entire); } - override const string toString() + override string toString() const { string s; if (line != 0) s = format("Line %d, column %d: ",line,column); diff --git a/std/zip.d b/std/zip.d index a86738a578e..ea38364092e 100644 --- a/std/zip.d +++ b/std/zip.d @@ -20,7 +20,7 @@ * WIKI = Phobos/StdZip * * Copyright: Copyright Digital Mars 2000 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * Source: $(PHOBOSSRC std/_zip.d) */ @@ -32,13 +32,6 @@ */ module std.zip; -import std.zlib; -import std.datetime; -import core.bitop; -import std.conv; -import std.algorithm; -import std.bitmanip : littleEndianToNative, nativeToLittleEndian; - //debug=print; /** Thrown on error. @@ -65,6 +58,9 @@ enum CompressionMethod : ushort */ final class ArchiveMember { + import std.conv : to, octal; + import std.datetime : DosFileTime, SysTime, SysTimeToDosFileTime; + /** * Read/Write: Usually the file name of the archive member; it is used to * index the archive directory for the member. Each member must have a unique @@ -259,6 +255,12 @@ final class ArchiveMember */ final class ZipArchive { + import std.bitmanip : littleEndianToNative, nativeToLittleEndian; + import std.algorithm : max; + import std.conv : to; + import std.zlib : compress; + import std.datetime : DosFileTime; + string comment; /// Read/Write: the archive comment. Must be less than 65536 bytes in length. private ubyte[] _data; @@ -268,6 +270,11 @@ final class ZipArchive private uint _diskStartDir; private uint _numEntries; private uint _totalEntries; + private bool _isZip64; + static const ushort zip64ExtractVersion = 45; + static const int digiSignLength = 6; + static const int eocd64LocLength = 20; + static const int eocd64Length = 56; /// Read Only: array representing the entire contents of the archive. @property ubyte[] data() { return _data; } @@ -281,6 +288,12 @@ final class ZipArchive /// Read Only: number of ArchiveMembers in the directory. @property uint numEntries() { return _numEntries; } @property uint totalEntries() { return _totalEntries; } /// ditto + + /// True when the archive is in Zip64 format. + @property bool isZip64() { return _isZip64; } + + /// Set this to true to force building a Zip64 archive. + @property void isZip64(bool value) { _isZip64 = value; } /** * Read Only: array indexed by the name of each member of the archive. * All the members of the archive can be accessed with a foreach loop: @@ -375,6 +388,11 @@ final class ZipArchive } assert(de._compressedData.length == de._compressedSize); + if (to!ulong(archiveSize) + 30 + de.name.length + de.extra.length + de.compressedSize + + directorySize + 46 + de.name.length + de.extra.length + de.comment.length + + 22 + comment.length + eocd64LocLength + eocd64Length > uint.max) + throw new ZipException("zip files bigger than 4 GB are unsupported"); + archiveSize += 30 + de.name.length + de.extra.length + de.compressedSize; @@ -383,7 +401,13 @@ final class ZipArchive de.comment.length; } - _data = new ubyte[archiveSize + directorySize + 22 + comment.length]; + if (!isZip64 && _directory.length > ushort.max) + _isZip64 = true; + uint dataSize = archiveSize + directorySize + 22 + cast(uint)comment.length; + if (isZip64) + dataSize += eocd64LocLength + eocd64Length; + + _data = new ubyte[dataSize]; // Populate the data[] @@ -445,13 +469,37 @@ final class ZipArchive } _totalEntries = numEntries; + if (isZip64) + { + // Write zip64 end of central directory record + uint eocd64Offset = i; + _data[i .. i + 4] = cast(ubyte[])"PK\x06\x06"; + putUlong (i + 4, eocd64Length - 12); + putUshort(i + 12, zip64ExtractVersion); + putUshort(i + 14, zip64ExtractVersion); + putUint (i + 16, diskNumber); + putUint (i + 20, diskStartDir); + putUlong (i + 24, numEntries); + putUlong (i + 32, totalEntries); + putUlong (i + 40, directorySize); + putUlong (i + 48, directoryOffset); + i += eocd64Length; + + // Write zip64 end of central directory record locator + _data[i .. i + 4] = cast(ubyte[])"PK\x06\x07"; + putUint (i + 4, diskNumber); + putUlong (i + 8, eocd64Offset); + putUint (i + 16, 1); + i += eocd64LocLength; + } + // Write end record endrecOffset = i; _data[i .. i + 4] = cast(ubyte[])"PK\x05\x06"; putUshort(i + 4, cast(ushort)diskNumber); putUshort(i + 6, cast(ushort)diskStartDir); - putUshort(i + 8, cast(ushort)numEntries); - putUshort(i + 10, cast(ushort)totalEntries); + putUshort(i + 8, (numEntries > ushort.max ? ushort.max : cast(ushort)numEntries)); + putUshort(i + 10, (totalEntries > ushort.max ? ushort.max : cast(ushort)totalEntries)); putUint (i + 12, directorySize); putUint (i + 16, directoryOffset); putUshort(i + 20, cast(ushort)comment.length); @@ -482,34 +530,85 @@ final class ZipArchive */ this(void[] buffer) - { int iend; - int i; + { uint iend; + uint i; int endcommentlength; uint directorySize; uint directoryOffset; this._data = cast(ubyte[]) buffer; + if (data.length > uint.max - 2) + throw new ZipException("zip files bigger than 4 GB are unsupported"); + // Find 'end record index' by searching backwards for signature - iend = to!uint(data.length) - 66000; - if (iend < 0) - iend = 0; + iend = (data.length > 66000 ? to!uint(data.length - 66000) : 0); for (i = to!uint(data.length) - 22; 1; i--) { - if (i < iend) + if (i < iend || i >= data.length) throw new ZipException("no end record"); if (_data[i .. i + 4] == cast(ubyte[])"PK\x05\x06") { endcommentlength = getUshort(i + 20); - if (i + 22 + endcommentlength > data.length) + if (i + 22 + endcommentlength > data.length + || i + 22 + endcommentlength < i) continue; comment = cast(string)(_data[i + 22 .. i + 22 + endcommentlength]); endrecOffset = i; + + uint k = i - eocd64LocLength; + if (k < i && _data[k .. k + 4] == cast(ubyte[])"PK\x06\x07") + { + _isZip64 = true; + i = k; + } + break; } } + if (isZip64) + { + // Read Zip64 record data + uint eocd64LocStart = i; + ulong eocdOffset = getUlong(i + 8); + if (eocdOffset + eocd64Length > _data.length) + throw new ZipException("corrupted directory"); + + i = to!uint(eocdOffset); + if (_data[i .. i + 4] != cast(ubyte[])"PK\x06\x06") + throw new ZipException("invalid Zip EOCD64 signature"); + + ulong eocd64Size = getUlong(i + 4); + if (eocd64Size + i - 12 > data.length) + throw new ZipException("invalid Zip EOCD64 size"); + + _diskNumber = getUint(i + 16); + _diskStartDir = getUint(i + 20); + + ulong numEntriesUlong = getUlong(i + 24); + ulong totalEntriesUlong = getUlong(i + 32); + ulong directorySizeUlong = getUlong(i + 40); + ulong directoryOffsetUlong = getUlong(i + 48); + + if (numEntriesUlong > uint.max) + throw new ZipException("supposedly more than 4294967296 files in archive"); + + if (numEntriesUlong != totalEntriesUlong) + throw new ZipException("multiple disk zips not supported"); + + if (directorySizeUlong > i || directoryOffsetUlong > i + || directorySizeUlong + directoryOffsetUlong > i) + throw new ZipException("corrupted directory"); + + _numEntries = to!uint(numEntriesUlong); + _totalEntries = to!uint(totalEntriesUlong); + directorySize = to!uint(directorySizeUlong); + directoryOffset = to!uint(directoryOffsetUlong); + } + else + { // Read end record data _diskNumber = getUshort(i + 4); _diskStartDir = getUshort(i + 6); @@ -525,6 +624,7 @@ final class ZipArchive if (directoryOffset + directorySize > i) throw new ZipException("corrupted directory"); + } i = directoryOffset; for (int n = 0; n < numEntries; n++) @@ -660,6 +760,12 @@ final class ZipArchive return littleEndianToNative!uint(result); } + ulong getUlong(int i) + { + ubyte[8] result = data[i .. i + 8]; + return littleEndianToNative!ulong(result); + } + void putUshort(int i, ushort us) { data[i .. i + 2] = nativeToLittleEndian(us); @@ -669,6 +775,11 @@ final class ZipArchive { data[i .. i + 4] = nativeToLittleEndian(ui); } + + void putUlong(int i, ulong ul) + { + data[i .. i + 8] = nativeToLittleEndian(ul); + } } debug(print) @@ -703,4 +814,37 @@ unittest auto zip3 = new ZipArchive(data1); zip3.build(); assert(zip3.directory["foo"].compressedSize == am1.compressedSize); + + // Test if packing and unpacking produces the original data + import std.random : uniform, MinstdRand0; + import std.stdio, std.conv; + MinstdRand0 gen; + const uint itemCount = 20, minSize = 10, maxSize = 500; + foreach (variant; 0..2) + { + bool useZip64 = !!variant; + zip1 = new ZipArchive(); + zip1.isZip64 = useZip64; + ArchiveMember[itemCount] ams; + foreach (i; 0..itemCount) + { + ams[i] = new ArchiveMember(); + ams[i].name = to!string(i); + ams[i].expandedData = new ubyte[](uniform(minSize, maxSize)); + foreach (ref ubyte c; ams[i].expandedData) + c = cast(ubyte)(uniform(0, 256)); + ams[i].compressionMethod(CompressionMethod.deflate); + zip1.addMember(ams[i]); + } + auto zippedData = zip1.build(); + zip2 = new ZipArchive(zippedData); + assert(zip2.isZip64 == useZip64); + foreach (am; ams) + { + am2 = zip2.directory[am.name]; + zip2.expand(am2); + assert(am.crc32 == am2.crc32); + assert(am.expandedData == am2.expandedData); + } + } } diff --git a/std/zlib.d b/std/zlib.d index eda2a56c5ca..def30df6864 100644 --- a/std/zlib.d +++ b/std/zlib.d @@ -1,16 +1,16 @@ // Written in the D programming language. /** - * Compress/decompress data using the $(LINK2 http://www._zlib.net, zlib library). + * Compress/decompress data using the $(WEB www.zlib.net, zlib library). * * References: - * $(LINK2 http://en.wikipedia.org/wiki/Zlib, Wikipedia) + * $(WEB en.wikipedia.org/wiki/Zlib, Wikipedia) * * Macros: * WIKI = Phobos/StdZlib * * Copyright: Copyright Digital Mars 2000 - 2011. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * Source: $(PHOBOSSRC std/_zlib.d) */ @@ -23,7 +23,7 @@ module std.zlib; //debug=zlib; // uncomment to turn on debugging printf's -private import etc.c.zlib, std.conv; +import etc.c.zlib; // Values for 'mode' @@ -163,6 +163,7 @@ const(void)[] compress(const(void)[] buf) void[] uncompress(void[] srcbuf, size_t destlen = 0u, int winbits = 15) { + import std.conv : to; int err; ubyte[] destbuf; @@ -267,6 +268,8 @@ enum HeaderFormat { class Compress { + import std.conv: to; + private: z_stream zs; int level = Z_DEFAULT_COMPRESSION; @@ -429,6 +432,8 @@ class Compress class UnCompress { + import std.conv: to; + private: z_stream zs; int inited; diff --git a/unittest.d b/unittest.d index 4e2e8abfac2..4ff4c335089 100644 --- a/unittest.d +++ b/unittest.d @@ -5,7 +5,7 @@ * tests on them. Then, it prints out the arguments passed to main(). * * Copyright: Copyright Digital Mars 2000 - 2009. - * License: Boost License 1.0. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(WEB digitalmars.com, Walter Bright) * * Copyright Digital Mars 2000 - 2009. @@ -73,8 +73,9 @@ int main(char[][] args) uint ranseed = std.random.unpredictableSeed; thisTid; int[] a; - a.reverse; // adi - a.sort; // qsort + import std.algorithm : sort, reverse; + reverse(a); // adi + sort(a); // qsort Clock.currTime(); // datetime Exception e = new ReadException(""); // stream din.eof(); // cstream @@ -96,7 +97,7 @@ int main(char[][] args) x[0] = 3; x[1] = 45; x[2] = -1; - x.sort; + sort(x[]); assert(x[0] == -1); assert(x[1] == 3); assert(x[2] == 45); diff --git a/win32.mak b/win32.mak index 185a98f9d66..4d0ea9a7231 100644 --- a/win32.mak +++ b/win32.mak @@ -38,13 +38,13 @@ CFLAGS=-mn -6 -r ## Flags for dmd D compiler -DFLAGS=-O -release -w +DFLAGS=-O -release -w -dip25 #DFLAGS=-unittest -g #DFLAGS=-unittest -cov -g ## Flags for compiling unittests -UDFLAGS=-O -w +UDFLAGS=-O -w -dip25 ## C compiler @@ -61,7 +61,7 @@ DMD=dmd ## Location of where to write the html documentation files DOCSRC = ../dlang.org -STDDOC = $(DOCSRC)/std.ddoc +STDDOC = $(DOCSRC)/html.ddoc $(DOCSRC)/dlang.org.ddoc $(DOCSRC)/std.ddoc $(DOCSRC)/macros.ddoc $(DOCSRC)/std_navbar-prerelease.ddoc project.ddoc DOC=..\..\html\d\phobos #DOC=..\doc\phobos @@ -108,52 +108,54 @@ SRC_STD_1_HEAVY= std\stdio.d std\stdiobase.d \ std\string.d std\format.d \ std\file.d -SRC_STD_2_HEAVY= std\range.d - SRC_STD_2a_HEAVY= std\array.d std\functional.d std\path.d std\outbuffer.d std\utf.d SRC_STD_3= std\csv.d std\math.d std\complex.d std\numeric.d std\bigint.d \ - std\bitmanip.d std\typecons.d \ - std\uni.d std\base64.d std\ascii.d \ - std\demangle.d std\uri.d std\mmfile.d std\getopt.d + std\bitmanip.d std\typecons.d \ + std\uni.d std\base64.d std\ascii.d \ + std\demangle.d std\uri.d std\metastrings.d std\mmfile.d std\getopt.d SRC_STD_3a= std\signals.d std\typetuple.d std\traits.d \ - std\encoding.d std\xml.d \ - std\random.d \ - std\exception.d \ - std\compiler.d \ - std\system.d std\concurrency.d + std\encoding.d std\xml.d \ + std\random.d \ + std\exception.d \ + std\compiler.d \ + std\system.d std\concurrency.d SRC_STD_3b= std\datetime.d #can't place SRC_STD_DIGEST in SRC_STD_REST because of out-of-memory issues SRC_STD_DIGEST= std\digest\crc.d std\digest\sha.d std\digest\md.d \ - std\digest\ripemd.d std\digest\digest.d - + std\digest\ripemd.d std\digest\digest.d + SRC_STD_CONTAINER= std\container\array.d std\container\binaryheap.d \ std\container\dlist.d std\container\rbtree.d std\container\slist.d \ std\container\util.d std\container\package.d - + SRC_STD_4= std\uuid.d $(SRC_STD_DIGEST) -SRC_STD_5_HEAVY= std\algorithm.d +SRC_STD_ALGO= std\algorithm\package.d std\algorithm\comparison.d \ + std\algorithm\iteration.d std\algorithm\mutation.d \ + std\algorithm\searching.d std\algorithm\setops.d \ + std\algorithm\sorting.d + +SRC_STD_5_HEAVY= $(SRC_STD_ALGO) SRC_STD_6= std\variant.d \ std\syserror.d std\zlib.d \ std\stream.d std\socket.d std\socketstream.d \ std\conv.d std\zip.d std\cstream.d \ - $(SRC_STD_CONTAINER) + $(SRC_STD_CONTAINER) $(SRC_STD_LOGGER) -SRC_STD_REST= std\regex.d \ - std\stdint.d \ +SRC_STD_REST= std\stdint.d \ std\json.d \ std\parallelism.d \ std\mathspecial.d \ std\process.d -SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2_HEAVY) $(SRC_STD_2a_HEAVY) \ +SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2a_HEAVY) \ $(SRC_STD_3) $(SRC_STD_3a) $(SRC_STD_3b) $(SRC_STD_4) \ - $(SRC_STD_5_HEAVY) $(SRC_STD_6) $(SRC_STD_REST) + $(SRC_STD_6) $(SRC_STD_REST) SRC= unittest.d index.d @@ -161,7 +163,7 @@ SRC_STD= std\zlib.d std\zip.d std\stdint.d std\conv.d std\utf.d std\uri.d \ std\math.d std\string.d std\path.d std\datetime.d \ std\csv.d std\file.d std\compiler.d std\system.d \ std\outbuffer.d std\base64.d \ - std\mmfile.d \ + std\metastrings.d std\mmfile.d \ std\syserror.d \ std\random.d std\stream.d std\process.d \ std\socket.d std\socketstream.d std\format.d \ @@ -170,14 +172,25 @@ SRC_STD= std\zlib.d std\zip.d std\stdint.d std\conv.d std\utf.d std\uri.d \ std\signals.d std\typetuple.d std\traits.d \ std\getopt.d \ std\variant.d std\numeric.d std\bitmanip.d std\complex.d std\mathspecial.d \ - std\functional.d std\algorithm.d std\array.d std\typecons.d \ + std\functional.d std\array.d std\typecons.d \ std\json.d std\xml.d std\encoding.d std\bigint.d std\concurrency.d \ - std\range.d std\stdiobase.d std\parallelism.d \ - std\regex.d \ + std\stdiobase.d std\parallelism.d \ std\exception.d std\ascii.d +SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\parser.d \ + std\regex\internal\tests.d std\regex\internal\backtracking.d \ + std\regex\internal\thompson.d std\regex\internal\kickstart.d \ + std\regex\internal\generator.d + +SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ + std\range\interfaces.d + SRC_STD_NET= std\net\isemail.d std\net\curl.d +SRC_STD_LOGGER= std\experimental\logger\core.d std\experimental\logger\filelogger.d \ + std\experimental\logger\multilogger.d std\experimental\logger\nulllogger.d \ + std\experimental\logger\package.d + SRC_STD_C= std\c\process.d std\c\stdlib.d std\c\time.d std\c\stdio.d \ std\c\math.d std\c\stdarg.d std\c\stddef.d std\c\fenv.d std\c\string.d \ std\c\locale.d std\c\wcharh.d @@ -198,7 +211,8 @@ SRC_STD_C_FREEBSD= std\c\freebsd\socket.d SRC_STD_INTERNAL= std\internal\cstring.d std\internal\processinit.d \ std\internal\unicode_tables.d std\internal\unicode_comp.d std\internal\unicode_decomp.d \ - std\internal\unicode_grapheme.d std\internal\unicode_norm.d std\internal\scopebuffer.d + std\internal\unicode_grapheme.d std\internal\unicode_norm.d std\internal\scopebuffer.d \ + std\internal\test\dummyrange.d SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d @@ -214,6 +228,7 @@ SRC_ETC= SRC_ETC_C= etc\c\zlib.d etc\c\curl.d etc\c\sqlite3.d SRC_TO_COMPILE_NOT_STD= \ + $(SRC_STD_REGEX) \ $(SRC_STD_NET) \ $(SRC_STD_C) \ $(SRC_STD_WIN) \ @@ -226,6 +241,8 @@ SRC_TO_COMPILE_NOT_STD= \ $(SRC_ETC_C) SRC_TO_COMPILE= $(SRC_STD_ALL) \ + $(SRC_STD_ALGO) \ + $(SRC_STD_RANGE) \ $(SRC_TO_COMPILE_NOT_STD) SRC_ZLIB= \ @@ -284,7 +301,13 @@ DOCS= $(DOC)\object.html \ $(DOC)\core_sync_mutex.html \ $(DOC)\core_sync_rwmutex.html \ $(DOC)\core_sync_semaphore.html \ - $(DOC)\std_algorithm.html \ + $(DOC)\std_algorithm_package.html \ + $(DOC)\std_algorithm_comparison.html \ + $(DOC)\std_algorithm_iteration.html \ + $(DOC)\std_algorithm_mutation.html \ + $(DOC)\std_algorithm_searching.html \ + $(DOC)\std_algorithm_setops.html \ + $(DOC)\std_algorithm_sorting.html \ $(DOC)\std_array.html \ $(DOC)\std_ascii.html \ $(DOC)\std_base64.html \ @@ -299,6 +322,7 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_container_rbtree.html \ $(DOC)\std_container_slist.html \ $(DOC)\std_container_package.html \ + $(DOC)\std_container_util.html \ $(DOC)\std_conv.html \ $(DOC)\std_digest_crc.html \ $(DOC)\std_digest_sha.html \ @@ -314,7 +338,6 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_file.html \ $(DOC)\std_format.html \ $(DOC)\std_functional.html \ - $(DOC)\std_gc.html \ $(DOC)\std_getopt.html \ $(DOC)\std_json.html \ $(DOC)\std_math.html \ @@ -326,7 +349,9 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_path.html \ $(DOC)\std_process.html \ $(DOC)\std_random.html \ - $(DOC)\std_range.html \ + $(DOC)\std_range_package.html \ + $(DOC)\std_range_primitives.html \ + $(DOC)\std_range_interfaces.html \ $(DOC)\std_regex.html \ $(DOC)\std_signals.html \ $(DOC)\std_socket.html \ @@ -336,7 +361,6 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_stream.html \ $(DOC)\std_string.html \ $(DOC)\std_system.html \ - $(DOC)\std_thread.html \ $(DOC)\std_traits.html \ $(DOC)\std_typecons.html \ $(DOC)\std_typetuple.html \ @@ -350,6 +374,11 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_zlib.html \ $(DOC)\std_net_isemail.html \ $(DOC)\std_net_curl.html \ + $(DOC)\std_experimental_logger_core.html \ + $(DOC)\std_experimental_logger_filelogger.html \ + $(DOC)\std_experimental_logger_multilogger.html \ + $(DOC)\std_experimental_logger_nulllogger.html \ + $(DOC)\std_experimental_logger_package.html \ $(DOC)\std_windows_charset.html \ $(DOC)\std_windows_registry.html \ $(DOC)\std_c_fenv.html \ @@ -373,13 +402,22 @@ $(LIB) : $(SRC_TO_COMPILE) \ $(DMD) -lib -of$(LIB) -Xfphobos.json $(DFLAGS) $(SRC_TO_COMPILE) \ $(ZLIB) $(DRUNTIMELIB) -UNITTEST_OBJS= unittest1.obj unittest2.obj unittest2a.obj \ - unittest3.obj unittest3a.obj unittest3b.obj unittest4.obj \ - unittest5.obj unittest6.obj unittest7.obj unittest8.obj +UNITTEST_OBJS= \ + unittest1.obj \ + unittest2.obj \ + unittest2a.obj \ + unittest3.obj \ + unittest3a.obj \ + unittest3b.obj \ + unittest4.obj \ + unittest5.obj \ + unittest6.obj \ + unittest7.obj \ + unittest8.obj unittest : $(LIB) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest1.obj $(SRC_STD_1_HEAVY) - $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2.obj $(SRC_STD_2_HEAVY) + $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2.obj $(SRC_STD_RANGE) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2a.obj $(SRC_STD_2a_HEAVY) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest3.obj $(SRC_STD_3) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest3a.obj $(SRC_STD_3a) @@ -391,7 +429,7 @@ unittest : $(LIB) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest8.obj $(SRC_TO_COMPILE_NOT_STD) $(DMD) $(UDFLAGS) -L/co -unittest unittest.d $(UNITTEST_OBJS) \ $(ZLIB) $(DRUNTIMELIB) - unittest + .\unittest.exe #unittest : unittest.exe # unittest @@ -409,7 +447,7 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -cov=95 -unittest -main -run std\string.d $(DMD) -cov=71 -unittest -main -run std\format.d $(DMD) -cov=83 -unittest -main -run std\file.d - $(DMD) -cov=86 -unittest -main -run std\range.d + $(DMD) -cov=86 -unittest -main -run std\range\package.d $(DMD) -cov=95 -unittest -main -run std\array.d $(DMD) -cov=100 -unittest -main -run std\functional.d $(DMD) -cov=96 -unittest -main -run std\path.d @@ -444,7 +482,13 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -cov=100 -unittest -main -run std\digest\md.d $(DMD) -cov=100 -unittest -main -run std\digest\ripemd.d $(DMD) -cov=75 -unittest -main -run std\digest\digest.d - $(DMD) -cov=95 -unittest -main -run std\algorithm.d + $(DMD) -cov=95 -unittest -main -run std\algorithm\package.d + $(DMD) -cov=95 -unittest -main -run std\algorithm\comparison.d + $(DMD) -cov=95 -unittest -main -run std\algorithm\iteration.d + $(DMD) -cov=95 -unittest -main -run std\algorithm\mutation.d + $(DMD) -cov=95 -unittest -main -run std\algorithm\searching.d + $(DMD) -cov=95 -unittest -main -run std\algorithm\setops.d + $(DMD) -cov=95 -unittest -main -run std\algorithm\sorting.d $(DMD) -cov=83 -unittest -main -run std\variant.d $(DMD) -cov=0 -unittest -main -run std\syserror.d $(DMD) -cov=58 -unittest -main -run std\zlib.d @@ -461,7 +505,7 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -cov=90 -unittest -main -run std\conv.d $(DMD) -cov=0 -unittest -main -run std\zip.d $(DMD) -cov=92 -unittest -main -run std\cstream.d - $(DMD) -cov=77 -unittest -main -run std\regex.d + $(DMD) -cov=77 -unittest -main -run std\regex\tests.d $(DMD) -cov=92 -unittest -main -run std\json.d $(DMD) -cov=87 -unittest -main -run std\parallelism.d $(DMD) -cov=50 -unittest -main -run std\mathspecial.d @@ -545,8 +589,26 @@ $(DOC)\core_sync_rwmutex.html : $(STDDOC) $(DRUNTIME)\src\core\sync\rwmutex.d $(DOC)\core_sync_semaphore.html : $(STDDOC) $(DRUNTIME)\src\core\sync\semaphore.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\core_sync_semaphore.html $(STDDOC) $(DRUNTIME)\src\core\sync\semaphore.d -I$(DRUNTIME)\src\ -$(DOC)\std_algorithm.html : $(STDDOC) std\algorithm.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm.html $(STDDOC) std\algorithm.d +$(DOC)\std_algorithm_package.html : $(STDDOC) std\algorithm\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_package.html $(STDDOC) std\algorithm\package.d + +$(DOC)\std_algorithm_comparison.html : $(STDDOC) std\algorithm\comparison.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_comparison.html $(STDDOC) std\algorithm\comparison.d + +$(DOC)\std_algorithm_iteration.html : $(STDDOC) std\algorithm\iteration.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_iteration.html $(STDDOC) std\algorithm\iteration.d + +$(DOC)\std_algorithm_mutation.html : $(STDDOC) std\algorithm\mutation.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_mutation.html $(STDDOC) std\algorithm\mutation.d + +$(DOC)\std_algorithm_searching.html : $(STDDOC) std\algorithm\searching.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_searching.html $(STDDOC) std\algorithm\searching.d + +$(DOC)\std_algorithm_setops.html : $(STDDOC) std\algorithm\setops.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_setops.html $(STDDOC) std\algorithm\setops.d + +$(DOC)\std_algorithm_sorting.html : $(STDDOC) std\algorithm\sorting.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_sorting.html $(STDDOC) std\algorithm\sorting.d $(DOC)\std_array.html : $(STDDOC) std\array.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_array.html $(STDDOC) std\array.d @@ -596,6 +658,15 @@ $(DOC)\std_container_util.html : $(STDDOC) std\container\util.d $(DOC)\std_container_package.html : $(STDDOC) std\container\package.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_container_package.html $(STDDOC) std\container\package.d +$(DOC)\std_range_package.html : $(STDDOC) std\range\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range_package.html $(STDDOC) std\range\package.d + +$(DOC)\std_range_primitives.html : $(STDDOC) std\range\primitives.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range_primitives.html $(STDDOC) std\range\primitives.d + +$(DOC)\std_range_interfaces.html : $(STDDOC) std\range\interfaces.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range_interfaces.html $(STDDOC) std\range\interfaces.d + $(DOC)\std_cstream.html : $(STDDOC) std\cstream.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_cstream.html $(STDDOC) std\cstream.d @@ -620,9 +691,6 @@ $(DOC)\std_format.html : $(STDDOC) std\format.d $(DOC)\std_functional.html : $(STDDOC) std\functional.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_functional.html $(STDDOC) std\functional.d -$(DOC)\std_gc.html : $(STDDOC) $(DRUNTIME)\src\core\memory.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_gc.html $(STDDOC) $(DRUNTIME)\src\core\memory.d - $(DOC)\std_getopt.html : $(STDDOC) std\getopt.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_getopt.html $(STDDOC) std\getopt.d @@ -656,11 +724,11 @@ $(DOC)\std_process.html : $(STDDOC) std\process.d $(DOC)\std_random.html : $(STDDOC) std\random.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_random.html $(STDDOC) std\random.d -$(DOC)\std_range.html : $(STDDOC) std\range.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range.html $(STDDOC) std\range.d +$(DOC)\std_range.html : $(STDDOC) std\range\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range.html $(STDDOC) std\range\package.d -$(DOC)\std_regex.html : $(STDDOC) std\regex.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_regex.html $(STDDOC) std\regex.d +$(DOC)\std_regex.html : $(STDDOC) std\regex\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_regex.html $(STDDOC) std\regex\package.d $(DOC)\std_signals.html : $(STDDOC) std\signals.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_signals.html $(STDDOC) std\signals.d @@ -686,9 +754,6 @@ $(DOC)\std_string.html : $(STDDOC) std\string.d $(DOC)\std_system.html : $(STDDOC) std\system.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_system.html $(STDDOC) std\system.d -$(DOC)\std_thread.html : $(STDDOC) $(DRUNTIME)\src\core\thread.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_thread.html $(STDDOC) -I$(DRUNTIME)\src $(DRUNTIME)\src\core\thread.d - $(DOC)\std_traits.html : $(STDDOC) std\traits.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_traits.html $(STDDOC) std\traits.d @@ -731,6 +796,21 @@ $(DOC)\std_net_isemail.html : $(STDDOC) std\net\isemail.d $(DOC)\std_net_curl.html : $(STDDOC) std\net\curl.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_net_curl.html $(STDDOC) std\net\curl.d +$(DOC)\std_experimental_logger_core.html : $(STDDOC) std\experimental\logger\core.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_core.html $(STDDOC) std\experimental\logger\core.d + +$(DOC)\std_experimental_logger_multilogger.html : $(STDDOC) std\experimental\logger\multilogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_multilogger.html $(STDDOC) std\experimental\logger\multilogger.d + +$(DOC)\std_experimental_logger_filelogger.html : $(STDDOC) std\experimental\logger\filelogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_filelogger.html $(STDDOC) std\experimental\logger\filelogger.d + +$(DOC)\std_experimental_logger_nulllogger.html : $(STDDOC) std\experimental\logger\nulllogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_nulllogger.html $(STDDOC) std\experimental\logger\nulllogger.d + +$(DOC)\std_experimental_logger_package.html : $(STDDOC) std\experimental\logger\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_package.html $(STDDOC) std\experimental\logger\package.d + $(DOC)\std_digest_crc.html : $(STDDOC) std\digest\crc.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_digest_crc.html $(STDDOC) std\digest\crc.d @@ -802,7 +882,8 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ $(SRC_STD_C_WIN) $(SRC_STD_C_LINUX) $(SRC_STD_C_OSX) $(SRC_STD_C_FREEBSD) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ - $(SRC_STD_INTERNAL_WINDOWS) + $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ + $(SRC_STD_LOGGER) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak $(STDDOC) zip32 -u phobos $(SRC) @@ -820,8 +901,12 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_ETC) $(SRC_ETC_C) zip32 -u phobos $(SRC_ZLIB) zip32 -u phobos $(SRC_STD_NET) + zip32 -u phobos $(SRC_STD_LOGGER) zip32 -u phobos $(SRC_STD_DIGEST) zip32 -u phobos $(SRC_STD_CONTAINER) + zip32 -u phobos $(SRC_STD_REGEX) + zip32 -u phobos $(SRC_STD_RANGE) + zip32 -u phobos $(SRC_STD_ALGO) phobos.zip : zip diff --git a/win64.mak b/win64.mak index c4d5f8ef2a1..5652b7521e6 100644 --- a/win64.mak +++ b/win64.mak @@ -37,13 +37,13 @@ CFLAGS=/O2 /nologo /I"$(VCDIR)\INCLUDE" /I"$(SDKDIR)\Include" ## Flags for dmd D compiler -DFLAGS=-m$(MODEL) -O -release -w +DFLAGS=-m$(MODEL) -O -release -w -dip25 #DFLAGS=-m$(MODEL) -unittest -g #DFLAGS=-m$(MODEL) -unittest -cov -g ## Flags for compiling unittests -UDFLAGS=-g -m$(MODEL) -O -w +UDFLAGS=-g -m$(MODEL) -O -w -dip25 ## C compiler, linker, librarian @@ -61,7 +61,7 @@ DMD=dmd ## Location of where to write the html documentation files DOCSRC = ../dlang.org -STDDOC = $(DOCSRC)/std.ddoc +STDDOC = $(DOCSRC)/html.ddoc $(DOCSRC)/dlang.org.ddoc $(DOCSRC)/std.ddoc $(DOCSRC)/macros.ddoc $(DOCSRC)/std_navbar-prerelease.ddoc project.ddoc DOC=..\..\html\d\phobos #DOC=..\doc\phobos @@ -108,8 +108,6 @@ SRC_STD_1_HEAVY= std\stdio.d std\stdiobase.d \ std\string.d std\format.d \ std\file.d -SRC_STD_2_HEAVY= std\range.d - SRC_STD_2a_HEAVY= std\array.d std\functional.d std\path.d std\outbuffer.d std\utf.d SRC_STD_math=std\math.d @@ -117,27 +115,31 @@ SRC_STD_3= std\csv.d std\complex.d std\numeric.d std\bigint.d SRC_STD_3c= std\datetime.d std\bitmanip.d std\typecons.d SRC_STD_3a= std\uni.d std\base64.d std\ascii.d \ - std\demangle.d std\uri.d std\mmfile.d std\getopt.d + std\demangle.d std\uri.d std\metastrings.d std\mmfile.d std\getopt.d SRC_STD_3b= std\signals.d std\typetuple.d std\traits.d \ - std\encoding.d std\xml.d \ - std\random.d \ - std\exception.d \ - std\compiler.d \ - std\system.d std\concurrency.d + std\encoding.d std\xml.d \ + std\random.d \ + std\exception.d \ + std\compiler.d \ + std\system.d std\concurrency.d #can't place SRC_STD_DIGEST in SRC_STD_REST because of out-of-memory issues SRC_STD_DIGEST= std\digest\crc.d std\digest\sha.d std\digest\md.d \ - std\digest\ripemd.d std\digest\digest.d + std\digest\ripemd.d std\digest\digest.d SRC_STD_CONTAINER= std\container\array.d std\container\binaryheap.d \ - std\container\dlist.d std\container\rbtree.d std\container\slist.d \ - std\container\util.d std\container\package.d - + std\container\dlist.d std\container\rbtree.d std\container\slist.d \ + std\container\util.d std\container\package.d + SRC_STD_4= std\uuid.d $(SRC_STD_DIGEST) +SRC_STD_ALGO= std\algorithm\package.d std\algorithm\comparison.d \ + std\algorithm\iteration.d std\algorithm\mutation.d \ + std\algorithm\searching.d std\algorithm\setops.d \ + std\algorithm\sorting.d -SRC_STD_5_HEAVY= std\algorithm.d +SRC_STD_5_HEAVY= $(SRC_STD_ALGO) SRC_STD_6a=std\variant.d SRC_STD_6b=std\syserror.d @@ -148,7 +150,6 @@ SRC_STD_6f=std\socketstream.d SRC_STD_6h=std\conv.d SRC_STD_6i=std\zip.d SRC_STD_6j=std\cstream.d -SRC_STD_6k=std\regex.d SRC_STD_7= \ std\stdint.d \ @@ -157,10 +158,9 @@ SRC_STD_7= \ std\mathspecial.d \ std\process.d -SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2_HEAVY) $(SRC_STD_2a_HEAVY) \ +SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2a_HEAVY) \ $(SRC_STD_math) \ $(SRC_STD_3) $(SRC_STD_3a) $(SRC_STD_3b) $(SRC_STD_3c) $(SRC_STD_4) \ - $(SRC_STD_5_HEAVY) \ $(SRC_STD_6a) \ $(SRC_STD_6b) \ $(SRC_STD_6c) \ @@ -171,8 +171,8 @@ SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2_HEAVY) $(SRC_STD_2a_HEAVY) \ $(SRC_STD_6h) \ $(SRC_STD_6i) \ $(SRC_STD_6j) \ - $(SRC_STD_6k) \ - $(SRC_STD_7) + $(SRC_STD_7) \ + $(SRC_STD_LOGGER) SRC= unittest.d index.d @@ -180,7 +180,7 @@ SRC_STD= std\zlib.d std\zip.d std\stdint.d std\conv.d std\utf.d std\uri.d \ std\math.d std\string.d std\path.d std\datetime.d \ std\csv.d std\file.d std\compiler.d std\system.d \ std\outbuffer.d std\base64.d \ - std\mmfile.d \ + std\metastrings.d std\mmfile.d \ std\syserror.d \ std\random.d std\stream.d std\process.d \ std\socket.d std\socketstream.d std\format.d \ @@ -189,14 +189,25 @@ SRC_STD= std\zlib.d std\zip.d std\stdint.d std\conv.d std\utf.d std\uri.d \ std\signals.d std\typetuple.d std\traits.d \ std\getopt.d \ std\variant.d std\numeric.d std\bitmanip.d std\complex.d std\mathspecial.d \ - std\functional.d std\algorithm.d std\array.d std\typecons.d \ + std\functional.d std\array.d std\typecons.d \ std\json.d std\xml.d std\encoding.d std\bigint.d std\concurrency.d \ - std\range.d std\stdiobase.d std\parallelism.d \ - std\regex.d \ + std\stdiobase.d std\parallelism.d \ std\exception.d std\ascii.d +SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\parser.d \ + std\regex\internal\tests.d std\regex\internal\backtracking.d \ + std\regex\internal\thompson.d std\regex\internal\kickstart.d \ + std\regex\internal\generator.d + +SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ + std\range\interfaces.d + SRC_STD_NET= std\net\isemail.d std\net\curl.d +SRC_STD_LOGGER= std\experimental\logger\core.d std\experimental\logger\filelogger.d \ + std\experimental\logger\multilogger.d std\experimental\logger\nulllogger.d \ + std\experimental\logger\package.d + SRC_STD_C= std\c\process.d std\c\stdlib.d std\c\time.d std\c\stdio.d \ std\c\math.d std\c\stdarg.d std\c\stddef.d std\c\fenv.d std\c\string.d \ std\c\locale.d std\c\wcharh.d @@ -217,7 +228,8 @@ SRC_STD_C_FREEBSD= std\c\freebsd\socket.d SRC_STD_INTERNAL= std\internal\cstring.d std\internal\processinit.d \ std\internal\unicode_tables.d std\internal\unicode_comp.d std\internal\unicode_decomp.d \ - std\internal\unicode_grapheme.d std\internal\unicode_norm.d std\internal\scopebuffer.d + std\internal\unicode_grapheme.d std\internal\unicode_norm.d std\internal\scopebuffer.d \ + std\internal\test\dummyrange.d SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d @@ -233,6 +245,7 @@ SRC_ETC= SRC_ETC_C= etc\c\zlib.d etc\c\curl.d etc\c\sqlite3.d SRC_TO_COMPILE_NOT_STD= \ + $(SRC_STD_REGEX) \ $(SRC_STD_NET) \ $(SRC_STD_C) \ $(SRC_STD_WIN) \ @@ -245,6 +258,8 @@ SRC_TO_COMPILE_NOT_STD= \ $(SRC_ETC_C) SRC_TO_COMPILE= $(SRC_STD_ALL) \ + $(SRC_STD_ALGO) \ + $(SRC_STD_RANGE) \ $(SRC_TO_COMPILE_NOT_STD) SRC_ZLIB= \ @@ -303,7 +318,13 @@ DOCS= $(DOC)\object.html \ $(DOC)\core_sync_mutex.html \ $(DOC)\core_sync_rwmutex.html \ $(DOC)\core_sync_semaphore.html \ - $(DOC)\std_algorithm.html \ + $(DOC)\std_algorithm_package.html \ + $(DOC)\std_algorithm_comparison.html \ + $(DOC)\std_algorithm_iteration.html \ + $(DOC)\std_algorithm_mutation.html \ + $(DOC)\std_algorithm_searching.html \ + $(DOC)\std_algorithm_setops.html \ + $(DOC)\std_algorithm_sorting.html \ $(DOC)\std_array.html \ $(DOC)\std_ascii.html \ $(DOC)\std_base64.html \ @@ -317,8 +338,8 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_container_dlist.html \ $(DOC)\std_container_rbtree.html \ $(DOC)\std_container_slist.html \ - $(DOC)\std_container_util.html \ $(DOC)\std_container_package.html \ + $(DOC)\std_container_util.html \ $(DOC)\std_conv.html \ $(DOC)\std_digest_crc.html \ $(DOC)\std_digest_sha.html \ @@ -334,7 +355,6 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_file.html \ $(DOC)\std_format.html \ $(DOC)\std_functional.html \ - $(DOC)\std_gc.html \ $(DOC)\std_getopt.html \ $(DOC)\std_json.html \ $(DOC)\std_math.html \ @@ -346,7 +366,9 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_path.html \ $(DOC)\std_process.html \ $(DOC)\std_random.html \ - $(DOC)\std_range.html \ + $(DOC)\std_range_package.html \ + $(DOC)\std_range_primitives.html \ + $(DOC)\std_range_interfaces.html \ $(DOC)\std_regex.html \ $(DOC)\std_signals.html \ $(DOC)\std_socket.html \ @@ -356,7 +378,6 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_stream.html \ $(DOC)\std_string.html \ $(DOC)\std_system.html \ - $(DOC)\std_thread.html \ $(DOC)\std_traits.html \ $(DOC)\std_typecons.html \ $(DOC)\std_typetuple.html \ @@ -370,6 +391,11 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_zlib.html \ $(DOC)\std_net_isemail.html \ $(DOC)\std_net_curl.html \ + $(DOC)\std_experimental_logger_core.html \ + $(DOC)\std_experimental_logger_filelogger.html \ + $(DOC)\std_experimental_logger_multilogger.html \ + $(DOC)\std_experimental_logger_nulllogger.html \ + $(DOC)\std_experimental_logger_package.html \ $(DOC)\std_windows_charset.html \ $(DOC)\std_windows_registry.html \ $(DOC)\std_c_fenv.html \ @@ -393,8 +419,11 @@ $(LIB) : $(SRC_TO_COMPILE) \ $(DMD) -lib -of$(LIB) -Xfphobos.json $(DFLAGS) $(SRC_TO_COMPILE) \ $(ZLIB) $(DRUNTIMELIB) -UNITTEST_OBJS= unittest1.obj unittest2.obj unittest2a.obj \ - unittestM.obj \ +UNITTEST_OBJS= \ + unittest1.obj \ + unittest2.obj \ + unittest2a.obj \ + unittestM.obj \ unittest3.obj \ unittest3a.obj \ unittest3b.obj \ @@ -411,12 +440,11 @@ UNITTEST_OBJS= unittest1.obj unittest2.obj unittest2a.obj \ unittest6h.obj \ unittest6i.obj \ unittest6j.obj \ - unittest6k.obj \ unittest7.obj unittest : $(LIB) $(DMD) $(UDFLAGS) -c -unittest -ofunittest1.obj $(SRC_STD_1_HEAVY) - $(DMD) $(UDFLAGS) -c -unittest -ofunittest2.obj $(SRC_STD_2_HEAVY) + $(DMD) $(UDFLAGS) -c -unittest -ofunittest2.obj $(SRC_STD_RANGE) $(DMD) $(UDFLAGS) -c -unittest -ofunittest2a.obj $(SRC_STD_2a_HEAVY) $(DMD) $(UDFLAGS) -c -unittest -ofunittestM.obj $(SRC_STD_math) $(DMD) $(UDFLAGS) -c -unittest -ofunittest3.obj $(SRC_STD_3) @@ -435,7 +463,6 @@ unittest : $(LIB) $(DMD) $(UDFLAGS) -c -unittest -ofunittest6f.obj $(SRC_STD_6f) $(DMD) $(UDFLAGS) -c -unittest -ofunittest6g.obj $(SRC_STD_CONTAINER) $(DMD) $(UDFLAGS) -c -unittest -ofunittest6j.obj $(SRC_STD_6j) - $(DMD) $(UDFLAGS) -c -unittest -ofunittest6k.obj $(SRC_STD_6k) $(DMD) $(UDFLAGS) -c -unittest -ofunittest7.obj $(SRC_STD_7) $(DMD) $(UDFLAGS) -c -unittest -ofunittest8.obj $(SRC_TO_COMPILE_NOT_STD) $(DMD) $(UDFLAGS) -L/OPT:NOICF -unittest unittest.d $(UNITTEST_OBJS) \ @@ -520,8 +547,26 @@ $(DOC)\core_sync_rwmutex.html : $(STDDOC) $(DRUNTIME)\src\core\sync\rwmutex.d $(DOC)\core_sync_semaphore.html : $(STDDOC) $(DRUNTIME)\src\core\sync\semaphore.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\core_sync_semaphore.html $(STDDOC) $(DRUNTIME)\src\core\sync\semaphore.d -I$(DRUNTIME)\src\ -$(DOC)\std_algorithm.html : $(STDDOC) std\algorithm.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm.html $(STDDOC) std\algorithm.d +$(DOC)\std_algorithm_package.html : $(STDDOC) std\algorithm\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_package.html $(STDDOC) std\algorithm\package.d + +$(DOC)\std_algorithm_comparison.html : $(STDDOC) std\algorithm\comparison.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_comparison.html $(STDDOC) std\algorithm\comparison.d + +$(DOC)\std_algorithm_iteration.html : $(STDDOC) std\algorithm\iteration.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_iteration.html $(STDDOC) std\algorithm\iteration.d + +$(DOC)\std_algorithm_mutation.html : $(STDDOC) std\algorithm\mutation.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_mutation.html $(STDDOC) std\algorithm\mutation.d + +$(DOC)\std_algorithm_searching.html : $(STDDOC) std\algorithm\searching.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_searching.html $(STDDOC) std\algorithm\searching.d + +$(DOC)\std_algorithm_setops.html : $(STDDOC) std\algorithm\setops.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_setops.html $(STDDOC) std\algorithm\setops.d + +$(DOC)\std_algorithm_sorting.html : $(STDDOC) std\algorithm\sorting.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_algorithm_sorting.html $(STDDOC) std\algorithm\sorting.d $(DOC)\std_array.html : $(STDDOC) std\array.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_array.html $(STDDOC) std\array.d @@ -571,6 +616,15 @@ $(DOC)\std_container_util.html : $(STDDOC) std\container\util.d $(DOC)\std_container_package.html : $(STDDOC) std\container\package.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_container_package.html $(STDDOC) std\container\package.d +$(DOC)\std_range_package.html : $(STDDOC) std\range\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range_package.html $(STDDOC) std\range\package.d + +$(DOC)\std_range_primitives.html : $(STDDOC) std\range\primitives.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range_primitives.html $(STDDOC) std\range\primitives.d + +$(DOC)\std_range_interfaces.html : $(STDDOC) std\range\interfaces.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range_interfaces.html $(STDDOC) std\range\interfaces.d + $(DOC)\std_cstream.html : $(STDDOC) std\cstream.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_cstream.html $(STDDOC) std\cstream.d @@ -595,9 +649,6 @@ $(DOC)\std_format.html : $(STDDOC) std\format.d $(DOC)\std_functional.html : $(STDDOC) std\functional.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_functional.html $(STDDOC) std\functional.d -$(DOC)\std_gc.html : $(STDDOC) $(DRUNTIME)\src\core\memory.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_gc.html $(STDDOC) $(DRUNTIME)\src\core\memory.d - $(DOC)\std_getopt.html : $(STDDOC) std\getopt.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_getopt.html $(STDDOC) std\getopt.d @@ -631,11 +682,11 @@ $(DOC)\std_process.html : $(STDDOC) std\process.d $(DOC)\std_random.html : $(STDDOC) std\random.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_random.html $(STDDOC) std\random.d -$(DOC)\std_range.html : $(STDDOC) std\range.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range.html $(STDDOC) std\range.d +$(DOC)\std_range.html : $(STDDOC) std\range\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_range.html $(STDDOC) std\range\package.d -$(DOC)\std_regex.html : $(STDDOC) std\regex.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_regex.html $(STDDOC) std\regex.d +$(DOC)\std_regex.html : $(STDDOC) std\regex\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_regex.html $(STDDOC) std\regex\package.d $(DOC)\std_signals.html : $(STDDOC) std\signals.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_signals.html $(STDDOC) std\signals.d @@ -661,9 +712,6 @@ $(DOC)\std_string.html : $(STDDOC) std\string.d $(DOC)\std_system.html : $(STDDOC) std\system.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_system.html $(STDDOC) std\system.d -$(DOC)\std_thread.html : $(STDDOC) $(DRUNTIME)\src\core\thread.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_thread.html $(STDDOC) -I$(DRUNTIME)\src $(DRUNTIME)\src\core\thread.d - $(DOC)\std_traits.html : $(STDDOC) std\traits.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_traits.html $(STDDOC) std\traits.d @@ -706,6 +754,21 @@ $(DOC)\std_net_isemail.html : $(STDDOC) std\net\isemail.d $(DOC)\std_net_curl.html : $(STDDOC) std\net\curl.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_net_curl.html $(STDDOC) std\net\curl.d +$(DOC)\std_experimental_logger_core.html : $(STDDOC) std\experimental\logger\core.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_core.html $(STDDOC) std\experimental\logger\core.d + +$(DOC)\std_experimental_logger_multilogger.html : $(STDDOC) std\experimental\logger\multilogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_multilogger.html $(STDDOC) std\experimental\logger\multilogger.d + +$(DOC)\std_experimental_logger_filelogger.html : $(STDDOC) std\experimental\logger\filelogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_filelogger.html $(STDDOC) std\experimental\logger\filelogger.d + +$(DOC)\std_experimental_logger_nulllogger.html : $(STDDOC) std\experimental\logger\nulllogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_nulllogger.html $(STDDOC) std\experimental\logger\nulllogger.d + +$(DOC)\std_experimental_logger_package.html : $(STDDOC) std\experimental\logger\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_package.html $(STDDOC) std\experimental\logger\package.d + $(DOC)\std_digest_crc.html : $(STDDOC) std\digest\crc.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_digest_crc.html $(STDDOC) std\digest\crc.d @@ -777,7 +840,8 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ $(SRC_STD_C_WIN) $(SRC_STD_C_LINUX) $(SRC_STD_C_OSX) $(SRC_STD_C_FREEBSD) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ - $(SRC_STD_INTERNAL_WINDOWS) + $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ + $(SRC_STD_LOGGER) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak $(STDDOC) zip32 -u phobos $(SRC) @@ -795,14 +859,18 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_ETC) $(SRC_ETC_C) zip32 -u phobos $(SRC_ZLIB) zip32 -u phobos $(SRC_STD_NET) + zip32 -u phobos $(SRC_STD_LOGGER) zip32 -u phobos $(SRC_STD_DIGEST) zip32 -u phobos $(SRC_STD_CONTAINER) + zip32 -u phobos $(SRC_STD_REGEX) + zip32 -u phobos $(SRC_STD_RANGE) + zip32 -u phobos $(SRC_STD_ALGO) phobos.zip : zip clean: cd etc\c\zlib - $(MAKE) -f win64.mak clean + $(MAKE) -f win$(MODEL).mak clean cd ..\..\.. del $(DOCS) del $(UNITTEST_OBJS) unittest.obj unittest.exe From f30ec34e1a06f937590a1b54972cace645730bdc Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 8 Feb 2015 17:23:40 +0300 Subject: [PATCH 03/46] fix comments --- std/internal/math/summation.d | 75 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/std/internal/math/summation.d b/std/internal/math/summation.d index 5c76508184f..a7d36d3a2f8 100644 --- a/std/internal/math/summation.d +++ b/std/internal/math/summation.d @@ -1,8 +1,8 @@ -/* - * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Ilya Yaroshenko - * Source: $(PHOBOSSRC std/internal/math/_summation.d) - */ +/++ + + License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). + + Authors: Ilya Yaroshenko + + Source: $(PHOBOSSRC std/internal/math/_summation.d) + +/ module std.internal.math.summation; import std.traits : @@ -19,7 +19,8 @@ private template isComplex(C) enum isComplex = is(C : Complex!F, F); } -private F fabs(F)(F f) //+-0, +-NaN, +-inf no matter +// FIXME: fabs in std.math avaliable only for for real. +private F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter { if(__ctfe) { @@ -40,9 +41,9 @@ private F fabs(F)(F f) //+-0, +-NaN, +-inf no matter } } -/** +/++ Naive summation algorithm. -*/ ++/ F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) if( isMutable!F && @@ -57,9 +58,9 @@ if( return s; } -/** +/++ $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. -*/ ++/ F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r) if( isMutable!F && @@ -78,10 +79,10 @@ if( } } -/** +/++ $(LUCKY Kahan summation) algorithm. -*/ -/** ++/ +/++ --------------------- s := x[1] c := 0 @@ -92,7 +93,7 @@ FOR k := 2 TO n DO s := t END DO --------------------- -*/ ++/ F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) if( isMutable!F && @@ -114,10 +115,10 @@ if( return s; } -/** +/++ $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). -*/ -/** ++/ +/++ --------------------- s := x[1] c := 0 @@ -132,7 +133,7 @@ FOR i := 2 TO n DO END DO s := s + c --------------------- -*/ ++/ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) if( isMutable!F && @@ -178,10 +179,10 @@ if( return s + c; } -/** +/++ $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). -*/ -/** ++/ +/++ --------------------- s := 0 ; cs := 0 ; ccs := 0 FOR j := 1 TO n DO @@ -203,7 +204,7 @@ FOR j := 1 TO n DO END FOR RETURN s+cs+ccs --------------------- -*/ ++/ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) if( isMutable!F && @@ -335,20 +336,20 @@ unittest assert(r == ar.sumKB2); } -/** +/++ Handler for full precise summation with $(D put) primitive. The current implementation re-establish special value semantics across iterations (i.e. handling -inf + inf). -*/ -/* ++/ +/++ Precise summation function as msum() by Raymond Hettinger in , enhanced with the exact partials sum and roundoff from Mark Dickinson's post at . See those links for more details, proofs and other references. -Note: IEEE 754R floating point semantics are assumed. -*/ +IEEE 754R floating point semantics are assumed. ++/ struct Summator(F, bool CTFEable = false) if(isFloatingPoint!F && is(Unqual!F == F)) { @@ -385,7 +386,7 @@ private: assert(partials[].map!fabs.isSorted); } - /** + /++ Compute the sum of a list of nonoverlapping floats. On input, partials is a list of nonzero, nonspecial, nonoverlapping floats, strictly increasing in magnitude, but @@ -395,7 +396,7 @@ private: rule). Two floating point values x and y are non-overlapping if the least significant nonzero bit of x is more significant than the most significant nonzero bit of y, or vice-versa. - */ + +/ static F partialsReduce(F s, in F[] partials) in { @@ -552,10 +553,10 @@ public: } } - /** + /++ Adds $(D x) to internal partial sums. - */ + +/ void unsafePut(F x) { size_t i; @@ -578,10 +579,10 @@ public: } } - /** + /++ Returns the value of the sum, rounded to the nearest representable floating-point number using the round-half-to-even rule. - */ + +/ F sum() const { debug(numeric) partialsDebug; @@ -633,8 +634,8 @@ public: return partialsReduce(y, parts); } - /** - */ + /++ + +/ F partialsSum() const { debug(numeric) partialsDebug; @@ -720,10 +721,10 @@ public: assert(N+N != double.infinity); } - /** + /++ $(D cast(F)) operator overlaoding. Returns $(D cast(T)sum()). See also: $(D extendTo) - */ + +/ T opCast(T)() if(Unqual!T == F) { return sum(); From 8c9a2d2cd6d0c9c138bcf724013952457ef1627a Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 8 Feb 2015 17:26:19 +0300 Subject: [PATCH 04/46] add std.numeric package --- std/{numeric.d => numeric/pacakge.d} | 0 std/{internal/math => numeric}/summation.d | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename std/{numeric.d => numeric/pacakge.d} (100%) rename std/{internal/math => numeric}/summation.d (100%) diff --git a/std/numeric.d b/std/numeric/pacakge.d similarity index 100% rename from std/numeric.d rename to std/numeric/pacakge.d diff --git a/std/internal/math/summation.d b/std/numeric/summation.d similarity index 100% rename from std/internal/math/summation.d rename to std/numeric/summation.d From c12a3dfa49f499adc3d0a8c41cb7f2604cb17436 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 8 Feb 2015 17:53:25 +0300 Subject: [PATCH 05/46] fix *.mak files --- posix.mak | 11 +++++++---- win32.mak | 27 ++++++++++++++++++--------- win64.mak | 24 ++++++++++++++++-------- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/posix.mak b/posix.mak index f4055057e2c..17be6755663 100644 --- a/posix.mak +++ b/posix.mak @@ -91,7 +91,7 @@ DOCSRC = ../dlang.org WEBSITE_DIR = ../web DOC_OUTPUT_DIR = $(WEBSITE_DIR)/phobos-prerelease BIGDOC_OUTPUT_DIR = /tmp -SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) std/regex/package $(EXTRA_DOCUMENTABLES) $(STD_LOGGER_MODULES)) +SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) std/regex/package $(EXTRA_DOCUMENTABLES) $(STD_LOGGER_MODULES)) $(STD_NUMERIC_MODULES) STDDOC = $(DOCSRC)/html.ddoc $(DOCSRC)/dlang.org.ddoc $(DOCSRC)/std_navbar-prerelease.ddoc $(DOCSRC)/std.ddoc $(DOCSRC)/macros.ddoc BIGSTDDOC = $(DOCSRC)/std_consolidated.ddoc $(DOCSRC)/macros.ddoc # Set DDOC, the documentation generator @@ -186,7 +186,7 @@ STD_MODULES = $(addprefix std/, array ascii base64 bigint \ bitmanip compiler complex concurrency conv \ cstream csv datetime demangle encoding exception \ file format functional getopt json math mathspecial \ - metastrings mmfile numeric outbuffer parallelism path \ + metastrings mmfile outbuffer parallelism path \ process random signals socket socketstream \ stdint stdio stdiobase stream string syserror system traits \ typecons typetuple uni uri utf uuid variant xml zip zlib) @@ -209,6 +209,8 @@ STD_DIGEST_MODULES = $(addprefix std/digest/, digest crc md ripemd sha) STD_CONTAINER_MODULES = $(addprefix std/container/, package array \ binaryheap dlist rbtree slist util) +STD_NUMERIC_MODULES = $(addprefix std/container/, package summation) + # OS-specific D modules EXTRA_MODULES_LINUX := $(addprefix std/c/linux/, linux socket) EXTRA_MODULES_OSX := $(addprefix std/c/osx/, socket) @@ -228,7 +230,7 @@ time wcharh) EXTRA_MODULES += $(EXTRA_DOCUMENTABLES) $(addprefix \ std/internal/digest/, sha_SSSE3 ) $(addprefix \ std/internal/math/, biguintcore biguintnoasm biguintx86 \ - gammafunction errorfunction summation) $(addprefix std/internal/, \ + gammafunction errorfunction) $(addprefix std/internal/, \ cstring processinit unicode_tables scopebuffer\ unicode_comp unicode_decomp unicode_grapheme unicode_norm) \ $(addprefix std/internal/test/, dummyrange) @@ -236,7 +238,8 @@ EXTRA_MODULES += $(EXTRA_DOCUMENTABLES) $(addprefix \ # Aggregate all D modules relevant to this build D_MODULES = $(STD_MODULES) $(EXTRA_MODULES) $(STD_NET_MODULES) \ $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_REGEX_MODULES) \ - $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) $(STD_LOGGER_MODULES) + $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) $(STD_LOGGER_MODULES) \ + $(STD_NUMERIC_MODULES) # Add the .d suffix to the module names D_FILES = $(addsuffix .d,$(D_MODULES)) diff --git a/win32.mak b/win32.mak index 4d0ea9a7231..07d45ab027b 100644 --- a/win32.mak +++ b/win32.mak @@ -110,7 +110,7 @@ SRC_STD_1_HEAVY= std\stdio.d std\stdiobase.d \ SRC_STD_2a_HEAVY= std\array.d std\functional.d std\path.d std\outbuffer.d std\utf.d -SRC_STD_3= std\csv.d std\math.d std\complex.d std\numeric.d std\bigint.d \ +SRC_STD_3= std\csv.d std\math.d std\complex.d std\bigint.d \ std\bitmanip.d std\typecons.d \ std\uni.d std\base64.d std\ascii.d \ std\demangle.d std\uri.d std\metastrings.d std\mmfile.d std\getopt.d @@ -171,7 +171,7 @@ SRC_STD= std\zlib.d std\zip.d std\stdint.d std\conv.d std\utf.d std\uri.d \ std\cstream.d std\demangle.d \ std\signals.d std\typetuple.d std\traits.d \ std\getopt.d \ - std\variant.d std\numeric.d std\bitmanip.d std\complex.d std\mathspecial.d \ + std\variant.d std\bitmanip.d std\complex.d std\mathspecial.d \ std\functional.d std\array.d std\typecons.d \ std\json.d std\xml.d std\encoding.d std\bigint.d std\concurrency.d \ std\stdiobase.d std\parallelism.d \ @@ -185,6 +185,8 @@ SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\pa SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ std\range\interfaces.d +SRC_STD_NUMERIC= std\numeric\pacakge.d std\numeric\summation.d + SRC_STD_NET= std\net\isemail.d std\net\curl.d SRC_STD_LOGGER= std\experimental\logger\core.d std\experimental\logger\filelogger.d \ @@ -218,8 +220,7 @@ SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d SRC_STD_INTERNAL_MATH= std\internal\math\biguintcore.d \ std\internal\math\biguintnoasm.d std\internal\math\biguintx86.d \ - std\internal\math\gammafunction.d std\internal\math\errorfunction.d \ - std\internal\math\summation.d + std\internal\math\gammafunction.d std\internal\math\errorfunction.d SRC_STD_INTERNAL_WINDOWS= std\internal\windows\advapi32.d @@ -243,6 +244,7 @@ SRC_TO_COMPILE_NOT_STD= \ SRC_TO_COMPILE= $(SRC_STD_ALL) \ $(SRC_STD_ALGO) \ $(SRC_STD_RANGE) \ + $(SRC_STD_NUMERIC) \ $(SRC_TO_COMPILE_NOT_STD) SRC_ZLIB= \ @@ -343,7 +345,8 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_math.html \ $(DOC)\std_mathspecial.html \ $(DOC)\std_mmfile.html \ - $(DOC)\std_numeric.html \ + $(DOC)\std_numeric_package.html \ + $(DOC)\std_numeric_summation.html \ $(DOC)\std_outbuffer.html \ $(DOC)\std_parallelism.html \ $(DOC)\std_path.html \ @@ -418,6 +421,7 @@ UNITTEST_OBJS= \ unittest : $(LIB) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest1.obj $(SRC_STD_1_HEAVY) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2.obj $(SRC_STD_RANGE) + $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2.obj $(SRC_STD_NUMERIC) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2a.obj $(SRC_STD_2a_HEAVY) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest3.obj $(SRC_STD_3) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest3a.obj $(SRC_STD_3a) @@ -456,7 +460,8 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -cov=93 -unittest -main -run std\csv.d $(DMD) -cov=91 -unittest -main -run std\math.d $(DMD) -cov=95 -unittest -main -run std\complex.d - $(DMD) -cov=70 -unittest -main -run std\numeric.d + $(DMD) -cov=70 -unittest -main -run std\numeric\pacakge.d + $(DMD) -cov=70 -unittest -main -run std\numeric\summation.d $(DMD) -cov=94 -unittest -main -run std\bigint.d $(DMD) -cov=95 -unittest -main -run std\bitmanip.d $(DMD) -cov=82 -unittest -main -run std\typecons.d @@ -706,8 +711,11 @@ $(DOC)\std_mathspecial.html : $(STDDOC) std\mathspecial.d $(DOC)\std_mmfile.html : $(STDDOC) std\mmfile.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_mmfile.html $(STDDOC) std\mmfile.d -$(DOC)\std_numeric.html : $(STDDOC) std\numeric.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric.html $(STDDOC) std\numeric.d +$(DOC)\std_numeric.html : $(STDDOC) std\numeric\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric_pacakge.html $(STDDOC) std\numeric\package.d + +$(DOC)\std_numeric_summation.html : $(STDDOC) std\numeric\summation.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric_summation.html $(STDDOC) std\numeric\summation.d $(DOC)\std_outbuffer.html : $(STDDOC) std\outbuffer.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_outbuffer.html $(STDDOC) std\outbuffer.d @@ -883,7 +891,7 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ - $(SRC_STD_LOGGER) + $(SRC_STD_LOGGER) $(SRC_STD_NUMERIC) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak $(STDDOC) zip32 -u phobos $(SRC) @@ -906,6 +914,7 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_STD_CONTAINER) zip32 -u phobos $(SRC_STD_REGEX) zip32 -u phobos $(SRC_STD_RANGE) + zip32 -u phobos $(SRC_STD_NUMERIC) zip32 -u phobos $(SRC_STD_ALGO) phobos.zip : zip diff --git a/win64.mak b/win64.mak index 5652b7521e6..48adf7cb815 100644 --- a/win64.mak +++ b/win64.mak @@ -111,7 +111,7 @@ SRC_STD_1_HEAVY= std\stdio.d std\stdiobase.d \ SRC_STD_2a_HEAVY= std\array.d std\functional.d std\path.d std\outbuffer.d std\utf.d SRC_STD_math=std\math.d -SRC_STD_3= std\csv.d std\complex.d std\numeric.d std\bigint.d +SRC_STD_3= std\csv.d std\complex.d std\bigint.d SRC_STD_3c= std\datetime.d std\bitmanip.d std\typecons.d SRC_STD_3a= std\uni.d std\base64.d std\ascii.d \ @@ -188,7 +188,7 @@ SRC_STD= std\zlib.d std\zip.d std\stdint.d std\conv.d std\utf.d std\uri.d \ std\cstream.d std\demangle.d \ std\signals.d std\typetuple.d std\traits.d \ std\getopt.d \ - std\variant.d std\numeric.d std\bitmanip.d std\complex.d std\mathspecial.d \ + std\variant.d std\bitmanip.d std\complex.d std\mathspecial.d \ std\functional.d std\array.d std\typecons.d \ std\json.d std\xml.d std\encoding.d std\bigint.d std\concurrency.d \ std\stdiobase.d std\parallelism.d \ @@ -202,6 +202,8 @@ SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\pa SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ std\range\interfaces.d +SRC_STD_NUMERIC= std\numeric\pacakge.d std\numeric\summation.d + SRC_STD_NET= std\net\isemail.d std\net\curl.d SRC_STD_LOGGER= std\experimental\logger\core.d std\experimental\logger\filelogger.d \ @@ -235,8 +237,7 @@ SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d SRC_STD_INTERNAL_MATH= std\internal\math\biguintcore.d \ std\internal\math\biguintnoasm.d std\internal\math\biguintx86.d \ - std\internal\math\gammafunction.d std\internal\math\errorfunction.d \ - std\internal\math\summation.d + std\internal\math\gammafunction.d std\internal\math\errorfunction.d SRC_STD_INTERNAL_WINDOWS= std\internal\windows\advapi32.d @@ -260,6 +261,7 @@ SRC_TO_COMPILE_NOT_STD= \ SRC_TO_COMPILE= $(SRC_STD_ALL) \ $(SRC_STD_ALGO) \ $(SRC_STD_RANGE) \ + $(SRC_STD_NUMERIC) \ $(SRC_TO_COMPILE_NOT_STD) SRC_ZLIB= \ @@ -360,7 +362,8 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_math.html \ $(DOC)\std_mathspecial.html \ $(DOC)\std_mmfile.html \ - $(DOC)\std_numeric.html \ + $(DOC)\std_numeric_package.html \ + $(DOC)\std_numeric_summation.html \ $(DOC)\std_outbuffer.html \ $(DOC)\std_parallelism.html \ $(DOC)\std_path.html \ @@ -445,6 +448,7 @@ UNITTEST_OBJS= \ unittest : $(LIB) $(DMD) $(UDFLAGS) -c -unittest -ofunittest1.obj $(SRC_STD_1_HEAVY) $(DMD) $(UDFLAGS) -c -unittest -ofunittest2.obj $(SRC_STD_RANGE) + $(DMD) $(UDFLAGS) -c -unittest -ofunittest2.obj $(SRC_STD_NUMERIC) $(DMD) $(UDFLAGS) -c -unittest -ofunittest2a.obj $(SRC_STD_2a_HEAVY) $(DMD) $(UDFLAGS) -c -unittest -ofunittestM.obj $(SRC_STD_math) $(DMD) $(UDFLAGS) -c -unittest -ofunittest3.obj $(SRC_STD_3) @@ -664,8 +668,11 @@ $(DOC)\std_mathspecial.html : $(STDDOC) std\mathspecial.d $(DOC)\std_mmfile.html : $(STDDOC) std\mmfile.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_mmfile.html $(STDDOC) std\mmfile.d -$(DOC)\std_numeric.html : $(STDDOC) std\numeric.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric.html $(STDDOC) std\numeric.d +$(DOC)\std_numeric.html : $(STDDOC) std\numeric\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric_pacakge.html $(STDDOC) std\numeric\package.d + +$(DOC)\std_numeric_summation.html : $(STDDOC) std\numeric\summation.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric_summation.html $(STDDOC) std\numeric\summation.d $(DOC)\std_outbuffer.html : $(STDDOC) std\outbuffer.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_outbuffer.html $(STDDOC) std\outbuffer.d @@ -841,7 +848,7 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ - $(SRC_STD_LOGGER) + $(SRC_STD_LOGGER) $(SRC_STD_NUMERIC) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak $(STDDOC) zip32 -u phobos $(SRC) @@ -864,6 +871,7 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_STD_CONTAINER) zip32 -u phobos $(SRC_STD_REGEX) zip32 -u phobos $(SRC_STD_RANGE) + zip32 -u phobos $(SRC_STD_NUMERIC) zip32 -u phobos $(SRC_STD_ALGO) phobos.zip : zip From ca3c563e110b9f7eca09a494ce8a5ee5bd13f67f Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 8 Feb 2015 17:58:54 +0300 Subject: [PATCH 06/46] fix posix.mak --- posix.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posix.mak b/posix.mak index 17be6755663..dfac882a13f 100644 --- a/posix.mak +++ b/posix.mak @@ -209,7 +209,7 @@ STD_DIGEST_MODULES = $(addprefix std/digest/, digest crc md ripemd sha) STD_CONTAINER_MODULES = $(addprefix std/container/, package array \ binaryheap dlist rbtree slist util) -STD_NUMERIC_MODULES = $(addprefix std/container/, package summation) +STD_NUMERIC_MODULES = $(addprefix std/numeric/, package summation) # OS-specific D modules EXTRA_MODULES_LINUX := $(addprefix std/c/linux/, linux socket) From ca20fd7ff3e255ea4a4f509188d3bdb1fabed4f0 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 8 Feb 2015 18:22:41 +0300 Subject: [PATCH 07/46] fix posix.mak --- posix.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posix.mak b/posix.mak index dfac882a13f..d77c9218555 100644 --- a/posix.mak +++ b/posix.mak @@ -91,7 +91,7 @@ DOCSRC = ../dlang.org WEBSITE_DIR = ../web DOC_OUTPUT_DIR = $(WEBSITE_DIR)/phobos-prerelease BIGDOC_OUTPUT_DIR = /tmp -SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) std/regex/package $(EXTRA_DOCUMENTABLES) $(STD_LOGGER_MODULES)) $(STD_NUMERIC_MODULES) +SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) std/regex/package $(EXTRA_DOCUMENTABLES) $(STD_LOGGER_MODULES) $(STD_NUMERIC_MODULES)) STDDOC = $(DOCSRC)/html.ddoc $(DOCSRC)/dlang.org.ddoc $(DOCSRC)/std_navbar-prerelease.ddoc $(DOCSRC)/std.ddoc $(DOCSRC)/macros.ddoc BIGSTDDOC = $(DOCSRC)/std_consolidated.ddoc $(DOCSRC)/macros.ddoc # Set DDOC, the documentation generator From 5dcc882e1073d4bd5f486558793f9cf21cbc60c5 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sun, 8 Feb 2015 19:12:11 +0300 Subject: [PATCH 08/46] fix posix.mak --- posix.mak | 3 +++ 1 file changed, 3 insertions(+) diff --git a/posix.mak b/posix.mak index d77c9218555..8bf469c9f55 100644 --- a/posix.mak +++ b/posix.mak @@ -445,6 +445,9 @@ $(DOC_OUTPUT_DIR)/std_algorithm_%.html : std/algorithm/%.d $(STDDOC) $(DOC_OUTPUT_DIR)/std_range_%.html : std/range/%.d $(STDDOC) $(DDOC) project.ddoc $(STDDOC) -Df$@ $< +$(DOC_OUTPUT_DIR)/std_numeric_%.html : std/numeric/%.d $(STDDOC) + $(DDOC) project.ddoc $(STDDOC) -Df$@ $< + $(DOC_OUTPUT_DIR)/std_regex_%.html : std/regex/%.d $(STDDOC) $(DDOC) project.ddoc $(STDDOC) -Df$@ $< From d511ea43d32e6a9abb10de172c4b089f5caf5d96 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 00:52:23 +0300 Subject: [PATCH 09/46] update code --- std/numeric/pacakge.d | 237 --------- std/numeric/summation.d | 1082 ++++++++++++++++++++++++--------------- 2 files changed, 667 insertions(+), 652 deletions(-) diff --git a/std/numeric/pacakge.d b/std/numeric/pacakge.d index 8f23ba698f5..7adc2362abd 100644 --- a/std/numeric/pacakge.d +++ b/std/numeric/pacakge.d @@ -1584,243 +1584,6 @@ unittest } -/** -Computes sum of range. -*/ -//TODO: CTFE for Precise. Needs CTFEScopeBuffer. -template fsum(SummationType, Summation summation = Summation.Precise) - if(isMutable!SummationType) -{ - static assert( - __traits(compiles, - { - SummationType a = 0.0, b, c; - c = a + b; - c = a - b; - static if(summation != Summation.Pairwise) - { - a += b; - a -= b; - } - }), summation.stringof~" isn't implemented for "~SummationType.stringof); - - - - SummationType fsum(Range)(Range r) - if( - isInputRange!Range && - isImplicitlyConvertible!(Unqual!(ForeachType!Range), SummationType) && - !isInfinite!Range && - ( - summation != Summation.Pairwise || - hasLength!Range && hasSlicing!Range - ) - ) - { - import std.internal.math.summation; - - static if(summation == Summation.Naive) - return sumNaive!(Range, SummationType)(r, SummationType(0.0)); - else static if(summation == Summation.Pairwise) - return sumPairwise!(Range, SummationType)(r); - else static if(summation == Summation.Kahan) - return sumKahan!(Range, SummationType)(r); - else static if(summation == Summation.KBN) - return sumKBN!(Range, SummationType)(r); - else static if(summation == Summation.KB2) - return sumKB2!(Range, SummationType)(r); - else static if(summation == Summation.Precise) - { - static if(isFloatingPoint!SummationType) - { - Summator!SummationType sum = 0; - foreach(e; r) - { - sum.put(e); - } - return sum.sum; - } - else - { - import std.complex : Complex; - static if(is(SummationType : Complex!F , F)) - { - static if(isForwardRange!Range) - { - auto s = r.save; - Summator!F sum = 0; - foreach(e; r) - { - sum.put(e.re); - } - F sumRe = sum.sum; - sum = 0; - foreach(e; s) - { - sum.put(e.im); - } - return SummationType(sumRe, sum.sum); - } - else - { - Summator!F sumRe = 0, sumIm = 0; - foreach(e; r) - { - sumRe.put(e.re); - sumIm.put(e.im); - } - return SummationType(sumRe.sum, sumIm.sum); - } - } - else static assert(0, "Precise summation isn't implemented for "~SummationType.stringof); - } - } - else static assert(0); - } -} - -///ditto -template fsum(Summation summation = Summation.Precise) -{ - Unqual!(ForeachType!Range) fsum(Range)(Range r) - { - return .fsum!(typeof(return), summation)(r); - } -} - -/// -unittest -{ - import std.math, std.algorithm, std.range; - auto ar = 1000 - .iota - .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) - .chain([-(1.7.pow(1000))]); - - //Summation.Precise is default - assert(ar.fsum == -1.0); - assert(ar.retro.fsum == -1.0); -} - -/// -unittest { - auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); - const r = 20000; - assert(r != ar.fsum!(Summation.Naive)); - assert(r != ar.fsum!(Summation.Pairwise)); - assert(r != ar.fsum!(Summation.Kahan)); - assert(r == ar.fsum!(Summation.KBN)); - assert(r == ar.fsum!(Summation.KB2)); - assert(r == ar.fsum); //Summation.Precise -} - -/** -$(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for summation user defined types. -*/ -unittest -{ - static struct Quaternion(F) - if(isFloatingPoint!F) - { - F[3] array; - - /// + and - operator overloading - Quaternion opBinary(string op)(auto ref Quaternion rhs) const - if(op == "+" || op == "-") - { - Quaternion ret = void; - foreach(i, ref e; ret.array) - mixin("e = array[i] "~op~" rhs.array[i];"); - return ret; - } - - /// += and -= operator overloading - Quaternion opOpAssign(string op)(auto ref Quaternion rhs) - if(op == "+" || op == "-") - { - Quaternion ret = void; - foreach(i, ref e; array) - mixin("e "~op~"= rhs.array[i];"); - return this; - } - - ///constructor with single FP argument - this(F f) - { - array[] = f; - } - } - - Quaternion!double q, p, r; - q.array = [0, 1, 2]; - p.array = [3, 4, 5]; - r.array = [3, 5, 7]; - - assert(r == [p, q].fsum!(Summation.Naive)); - assert(r == [p, q].fsum!(Summation.Pairwise)); - assert(r == [p, q].fsum!(Summation.Kahan)); -} - -/** -Data type for summation can be specified. -*/ -unittest { - static assert(is(typeof([1, 3, 2].fsum!double) == double)); - static assert(is(typeof([1.0,2.0].fsum!(float, Summation.KBN)) == float)); -} - -/** -All summation algorithms available for complex numbers. -*/ -unittest -{ - import std.complex; - Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; - Complex!double r = complex(10, 14); - assert(r == ar.fsum); -} - - -/** -Summation algorithms for ranges of floating point numbers or $(D Complex). -*/ -enum Summation -{ - /** - Naive summation algorithm. - */ - Naive, - - /** - $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. - */ - Pairwise, - - /** - $(LUCKY Kahan summation) algorithm. - */ - Kahan, - - /** - $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). - */ - KBN, - - /** - $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). - */ - KB2, - - /** - Full precision summation algorithm. - Returns the value of the sum, rounded to the nearest representable - floating-point number using the $(LUCKY round-half-to-even rule). - */ - Precise, - - -} - /** Normalizes values in $(D range) by multiplying each element with a number chosen such that values sum up to $(D sum). If elements in $(D diff --git a/std/numeric/summation.d b/std/numeric/summation.d index a7d36d3a2f8..4c7307c7018 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -3,388 +3,243 @@ + Authors: Ilya Yaroshenko + Source: $(PHOBOSSRC std/internal/math/_summation.d) +/ -module std.internal.math.summation; +module std.numeric.summation; -import std.traits : - Unqual, - isIterable, - isMutable, - isImplicitlyConvertible, - ForeachType, - isFloatingPoint; +import std.traits; +import std.typecons; +import std.range.primitives; -private template isComplex(C) +/** +Computes sum of range. +*/ +//TODO: CTFE for Precise. Needs CTFEScopeBuffer. +template fsum(F, Summation summation = Summation.Precise) + if (isFloatingPoint!F && isMutable!F) { - import std.complex : Complex; - enum isComplex = is(C : Complex!F, F); -} + alias sum = Algo!summation; -// FIXME: fabs in std.math avaliable only for for real. -private F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter -{ - if(__ctfe) + F fsum(Range)(Range r) { - return f < 0 ? -f : f; + return sum!(Range, typeof(return))(r); } - else + + F fsum(F, Range)(F seed, Range r) { - version(LDC) - { - import ldc.intrinsics : llvm_fabs; - return llvm_fabs(f); - } - else - { - import core.stdc.tgmath : fabs; - return fabs(f); - } + return sum!(Range, F)(r, seed); } + + //F fsum(Range)(Range r, F seed) + // if ( + // isInputRange!Range && + // isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && + // !isInfinite!Range && + // ( + // summation != Summation.Pairwise || + // hasLength!Range && hasSlicing!Range + // ) + // ) } -/++ -Naive summation algorithm. -+/ -F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) -if( - isMutable!F && - isIterable!Range && - isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) -) + //static assert( + // __traits(compiles, + // { + // F a = 0.0, b, c; + // c = a + b; + // c = a - b; + // static if (summation != Summation.Pairwise) + // { + // a += b; + // a -= b; + // } + // }), summation.stringof ~ " isn't implemented for " ~ F.stringof); + +///ditto +template fsum(Summation summation = Summation.Precise) { - foreach(F x; r) + alias sum = Algo!summation; + + Unqual!(ForeachType!Range) fsum(Range)(Range r) { - s += x; + return sum!(Range, typeof(return))(r); } - return s; -} -/++ -$(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. -+/ -F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r) -if( - isMutable!F && - isIterable!Range && - isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) -) -{ - import std.range : hasLength, hasSlicing; - static assert(hasLength!Range && hasSlicing!Range); - switch (r.length) + F fsum(F, Range)(F seed, Range r) { - case 0: return F(0); - case 1: return cast(F)r[0]; - case 2: return cast(F)(r[0] + cast(F)r[1]); - default: auto n = r.length/2; return cast(F)(sumPairwise!(Range, F)(r[0..n]) + sumPairwise!(Range, F)(r[n..$])); + return sum!(F, Range)(r, seed); } } -/++ -$(LUCKY Kahan summation) algorithm. -+/ -/++ ---------------------- -s := x[1] -c := 0 -FOR k := 2 TO n DO - y := x[k] - c - t := s + y - c := (t - s) - y - s := t -END DO ---------------------- -+/ -F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) -if( - isMutable!F && - isIterable!Range && - isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) -) +/// +unittest { - F c = 0.0; - F y; // do not declare in the loop (algo can be used for matrixes and etc) - F t; // ditto - foreach(F x; r) - { - y = x - c; - t = s + y; - c = t - s; - c -= y; - s = t; - } - return s; + import std.math, std.algorithm, std.range; + auto ar = 1000 + .iota + .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) + .chain([-(1.7.pow(1000))]); + + //Summation.Precise is default + assert(ar.fsum == -1.0); + assert(ar.retro.fsum == -1.0); } -/++ -$(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). -+/ -/++ ---------------------- -s := x[1] -c := 0 -FOR i := 2 TO n DO - t := s + x[i] - IF ABS(s) >= ABS(x[i]) THEN - c := c + ((s-t)+x[i]) - ELSE - c := c + ((x[i]-t)+s) - END IF - s := t -END DO -s := s + c ---------------------- -+/ -F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) -if( - isMutable!F && - isIterable!Range && - isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && - (isFloatingPoint!F || isComplex!F) -) +/// +unittest { + import std.algorithm; + auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); + const r = 20000; + assert(r != ar.fsum!(Summation.Naive)); + assert(r != ar.fsum!(Summation.Pairwise)); + assert(r != ar.fsum!(Summation.Kahan)); + assert(r == ar.fsum!(Summation.KBN)); + assert(r == ar.fsum!(Summation.KB2)); + assert(r == ar.fsum); //Summation.Precise +} + +/** +$(D Fast), $(D Pairwise) and $(D Kahan) algorithms can be used for summation user defined types. +*/ +unittest { - F c = 0.0; - static if(isFloatingPoint!F) - { - foreach(F x; r) - { - F t = s + x; - if(s.fabs >= x.fabs) - c += (s-t)+x; - else - c += (x-t)+s; - s = t; - } - } - else + static struct Quaternion(F) + if (isFloatingPoint!F) { - foreach(F x; r) + F[3] array; + + /// + and - operator overloading + Quaternion opBinary(string op)(auto ref Quaternion rhs) const + if (op == "+" || op == "-") { - F t = s + x; - if(s.re.fabs < x.re.fabs) - { - auto t_re = s.re; - s.re = x.re; - x.re = t_re; - } - if(s.im.fabs < x.im.fabs) - { - auto t_im = s.im; - s.im = x.im; - x.im = t_im; - } - c += (s-t)+x; - s = t; + Quaternion ret = void; + foreach(i, ref e; ret.array) + mixin("e = array[i] "~op~" rhs.array[i];"); + return ret; } - } - return s + c; -} -/++ -$(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). -+/ -/++ ---------------------- -s := 0 ; cs := 0 ; ccs := 0 -FOR j := 1 TO n DO - t := s + x[i] - IF ABS(s) >= ABS(x[i]) THEN - c := (s-t) + x[i] - ELSE - c := (x[i]-t) + s - END IF - s := t - t := cs + c - IF ABS(cs) >= ABS(c) THEN - cc := (cs-t) + c - ELSE - cc := (c-t) + cs - END IF - cs := t - ccs := ccs + cc -END FOR -RETURN s+cs+ccs ---------------------- -+/ -F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) -if( - isMutable!F && - isIterable!Range && - isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && - (isFloatingPoint!F || isComplex!F) -) -{ - F cs = 0.0; - F ccs = 0.0; - static if(isFloatingPoint!F) - { - foreach(F x; r) + /// += and -= operator overloading + Quaternion opOpAssign(string op)(auto ref Quaternion rhs) + if (op == "+" || op == "-") { - F t = s + x; - F c = void; - if(s.fabs >= x.fabs) - c = (s-t)+x; - else - c = (x-t)+s; - s = t; - t = cs + c; - if(cs.fabs >= c.fabs) - ccs += (cs-t)+c; - else - ccs += (c-t)+cs; - cs = t; + Quaternion ret = void; + foreach(i, ref e; array) + mixin("e "~op~"= rhs.array[i];"); + return this; } - } - else - { - foreach(F x; r) + + ///constructor with single FP argument + this(F f) { - F t = s + x; - if(s.re.fabs < x.re.fabs) - { - auto t_re = s.re; - s.re = x.re; - x.re = t_re; - } - if(s.im.fabs < x.im.fabs) - { - auto t_im = s.im; - s.im = x.im; - x.im = t_im; - } - F c = (s-t)+x; - s = t; - if(cs.re.fabs < c.re.fabs) - { - auto t_re = cs.re; - cs.re = c.re; - c.re = t_re; - } - if(cs.im.fabs < c.im.fabs) - { - auto t_im = cs.im; - cs.im = c.im; - c.im = t_im; - } - ccs += (cs-t)+c; - cs = t; + array[] = f; } } - return s+cs+ccs; // no rounding in between -} -unittest -{ - import std.typetuple; - foreach(I; TypeTuple!(byte, uint, long)) - { - I[] ar = [1, 2, 3, 4]; - I r = 10; - assert(r == ar.sumNaive); - assert(r == ar.sumPairwise); - } + Quaternion!double q, p, r; + q.array = [0, 1, 2]; + p.array = [3, 4, 5]; + r.array = [3, 5, 7]; + + assert(r == [p, q].fsum!(Summation.Fast)); + assert(r == [p, q].fsum!(Summation.Pairwise)); + assert(r == [p, q].fsum!(Summation.Kahan)); } -unittest -{ - import std.typetuple; - foreach(F; TypeTuple!(float, double, real)) - { - F[] ar = [1, 2, 3, 4]; - F r = 10; - assert(r == ar.sumNaive); - assert(r == ar.sumPairwise); - assert(r == ar.sumKahan); - assert(r == ar.sumKBN); - assert(r == ar.sumKB2); - } +/** +Data type for summation can be specified. +*/ +unittest { + alias fs = fsum!double; +// static assert(is(typeof([1, 3, 2].fs()) == double)); + static assert(is(typeof([1.0,2.0].fsum!(float, Summation.KBN)()) == float), typeof([1.0,2.0].fsum!(float, Summation.KBN)()).stringof); } +/** +All summation algorithms available for complex numbers. +*/ unittest { import std.complex; Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; Complex!double r = complex(10, 14); - assert(r == ar.sumNaive); - assert(r == ar.sumPairwise); - assert(r == ar.sumKahan); - assert(r == ar.sumKBN); - assert(r == ar.sumKB2); + assert(r == ar.fsum); } -//BUG: DMD 2.066 Segmentation fault (core dumped) -//unittest -//{ -// import core.simd; -// static if(__traits(compiles, double2.init + double2.init)) -// { -// double2[] ar = [double2([1.0, 2]), double2([2, 3]), double2([3, 4]), double2([4, 6])]; -// assert(ar.sumNaive.array == double2([10, 14]).array); -// assert(ar.sumPairwise.array == double2([10, 14]).array); -// assert(ar.sumKahan.array == double2([10, 14]).array); -// } -//} -unittest +/** +Summation algorithms for ranges of floating point numbers or $(D Complex). +*/ +enum Summation { - import std.algorithm : map; - auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); - double r = 20000; - assert(r != ar.sumNaive); - assert(r != ar.sumPairwise); - assert(r != ar.sumKahan); - assert(r == ar.sumKBN); - assert(r == ar.sumKB2); + /** + Fast summation algorithm. + */ + Fast, + + /** + Naive algorithm. + */ + Naive, + + /** + $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. + */ + Pairwise, + + /** + $(LUCKY Kahan summation) algorithm. + */ + Kahan, + + /** + $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). + */ + KBN, + + /** + $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). + */ + KB2, + + /** + Full precision summation algorithm. + Returns the value of the sum, rounded to the nearest representable + floating-point number using the $(LUCKY round-half-to-even rule). + */ + Precise, } + /++ Handler for full precise summation with $(D put) primitive. The current implementation re-establish special value semantics across iterations (i.e. handling -inf + inf). + +References: $(LINK2 http://www.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps, +"Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates", Jonathan Richard Shewchuk) +/ -/++ +/+ Precise summation function as msum() by Raymond Hettinger in , enhanced with the exact partials sum and roundoff from Mark Dickinson's post at . See those links for more details, proofs and other references. - IEEE 754R floating point semantics are assumed. +/ -struct Summator(F, bool CTFEable = false) -if(isFloatingPoint!F && is(Unqual!F == F)) + import std.math : isNaN, isFinite, isInfinity, signbit; + +struct Summator(F) + if (isFloatingPoint!F && isMutable!F) { import std.internal.scopebuffer; - static if(CTFEable) import std.conv; + private: enum F M = (cast(F)(2)) ^^ (F.max_exp - 1); - - static if(CTFEable) - { - static assert(0, "CTFEable Summator not implemented."); - } - else - { - F[32] scopeBufferArray = void; - ScopeBuffer!F partials; - } - + F[32] scopeBufferArray = void; + ScopeBuffer!F partials; //sum for NaN and infinity. F s; //Overflow Degree. Count of 2^^F.max_exp minus count of -(2^^F.max_exp) sizediff_t o; - debug(numeric) - void partialsDebug() const - { - foreach(y; partials[]) - { - assert(y); - assert(y.isFinite); - } - //TODO: Add NonOverlaping check to std.math - import std.algorithm : isSorted, map; - assert(partials[].map!fabs.isSorted); - } /++ Compute the sum of a list of nonoverlapping floats. @@ -408,7 +263,7 @@ private: foreach_reverse(i, y; partials) { s = partialsReducePred(s, y, i ? partials[i-1] : 0, _break); - if(_break) + if (_break) break; debug(numeric) assert(s.isFinite); } @@ -432,13 +287,13 @@ private: assert(s.isFinite); assert(fabs(y) < fabs(x)); } - if(l) + if (l) { //Make half-even rounding work across multiple partials. //Needed so that sum([1e-16, 1, 1e16]) will round-up the last //digit to two instead of down to zero (the 1e-16 makes the 1 //slightly closer to two). Can guarantee commutativity. - if(z && !signbit(l * z)) + if (z && !signbit(l * z)) { l *= 2; x = s + l; @@ -453,9 +308,9 @@ private: //Returns corresponding infinity if is overflow and 0 otherwise. F overflow() const { - if(o == 0) + if (o == 0) return 0; - if(partials.length && (o == -1 || o == 1) && signbit(o * partials[$-1])) + if (partials.length && (o == -1 || o == 1) && signbit(o * partials[$-1])) { // problem case: decide whether result is representable F x = o * M; @@ -463,7 +318,7 @@ private: F h = x + y; F l = (y - (h - x)) * 2; y = h * 2; - if(!y.isInfinity || partials.length > 1 && !signbit(l * partials[$-2]) && (h + l) - h == l) + if (!.isInfinity(y) || partials.length > 1 && !signbit(l * partials[$-2]) && (h + l) - h == l) return 0; } return F.infinity * o; @@ -472,12 +327,12 @@ private: public: /// - this(F f) + this(F x) { partials = scopeBuffer(scopeBufferArray); s = 0; o = 0; - if(f) put(f); + if (x) put(x); } /// @@ -493,7 +348,7 @@ public: this(this) { auto a = partials[]; - if(scopeBufferArray.ptr !is a.ptr) + if (scopeBufferArray.ptr !is a.ptr) { partials = scopeBuffer(scopeBufferArray); partials.put(a); @@ -503,20 +358,20 @@ public: ///Adds $(D x) to internal partial sums. void put(F x) { - if(x.isFinite) + if (.isFinite(x)) { size_t i; foreach(y; partials[]) { F h = x + y; - if(h.isInfinity) + if (.isInfinity(h)) { - if(fabs(x) < fabs(y)) + if (fabs(x) < fabs(y)) { F t = x; x = y; y = t; } //h == -F.infinity - if(h.signbit) + if (signbit(h)) { x += M; x += M; @@ -535,14 +390,14 @@ public: debug(numeric) assert(h.isFinite); F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); debug(numeric) assert(l.isFinite); - if(l) + if (l) { partials[i++] = l; } x = h; } partials.length = i; - if(x) + if (x) { partials.put(x); } @@ -554,8 +409,7 @@ public: } /++ - Adds $(D x) to internal partial sums. - + Adds $(D x) to the internal partial sums. +/ void unsafePut(F x) { @@ -566,14 +420,14 @@ public: debug(numeric) assert(h.isFinite); F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); debug(numeric) assert(l.isFinite); - if(l) + if (l) { partials[i++] = l; } x = h; } partials.length = i; - if(x) + if (x) { partials.put(x); } @@ -583,24 +437,34 @@ public: Returns the value of the sum, rounded to the nearest representable floating-point number using the round-half-to-even rule. +/ - F sum() const + F sum() const { - debug(numeric) partialsDebug; - - if(s) + debug(numeric) + { + foreach(y; partials[]) + { + assert(y); + assert(y.isFinite); + } + //TODO: Add Non-Overlapping check to std.math + import std.algorithm : isSorted, map; + assert(partials[].map!(a => fabs(a)).isSorted); + } + + if (s) return s; auto parts = partials[]; F y = 0; //pick last - if(parts.length) + if (parts.length) { y = parts[$-1]; parts = parts[0..$-1]; } - if(o) + if (o) { immutable F of = o; - if(y && (o == -1 || o == 1) && signbit(of * y)) + if (y && (o == -1 || o == 1) && signbit(of * y)) { // problem case: decide whether result is representable y /= 2; @@ -608,7 +472,7 @@ public: immutable F h = x + y; F l = (y - (h - x)) * 2; y = h * 2; - if(y.isInfinity) + if (y.isInfinity) { // overflow, except in edge case... x = h + l; @@ -617,11 +481,11 @@ public: F.infinity * of; parts = null; } - else if(l) + else if (l) { bool _break; y = partialsReducePred(y, l, parts.length ? parts[$-1] : 0, _break); - if(_break) + if (_break) parts = null; } } @@ -634,49 +498,30 @@ public: return partialsReduce(y, parts); } - /++ - +/ - F partialsSum() const - { - debug(numeric) partialsDebug; - auto parts = partials[]; - F y = 0; - //pick last - if(parts.length) - { - y = parts[$-1]; - parts = parts[0..$-1]; - } - return partialsReduce(y, parts); - } - - /// - @property F nonFinitySum() const - { - return s; - } - - /// - @property sizediff_t overflowDegree() const - { - return o; - } - - /// - void resetNonPartials() - { - s = 0; - o = 0; - } + ///++ + //+/ + //F partialsSum() const + //{ + // debug(numeric) partialsDebug; + // auto parts = partials[]; + // F y = 0; + // //pick last + // if (parts.length) + // { + // y = parts[$-1]; + // parts = parts[0..$-1]; + // } + // return partialsReduce(y, parts); + //} ///Returns $(D Summator) with extended internal partial sums. - Summator!(Unqual!E) extendTo(E)() if( - isFloatingPoint!E && - E.max_exp >= F.max_exp && - E.mant_dig >= F.mant_dig + T opCast(T : Summator!P, P)() if ( + isMutable!T && + P.max_exp >= F.max_exp && + P.mant_dig >= F.mant_dig ) { - static if(is(Unqual!E == F)) + static if (is(P == F)) return this; else { @@ -688,18 +533,25 @@ public: { ret.partials.put(p); } - enum exp_diff = E.max_exp - F.max_exp; - static if(exp_diff) + //pragma(msg, P.max_exp); + //pragma(msg, F.max_exp); + pragma(msg, P.max_exp / F.max_exp); + enum exp_diff = P.max_exp / F.max_exp; + static if (exp_diff) { - if(ret.o) + if (ret.o) { - //enum shift = 2u ^^ exp_diff; - immutable f = ret.o >> exp_diff; - immutable t = cast(int)(ret.o - (f << exp_diff)); + immutable f = ret.o / exp_diff; + immutable t = cast(int)(ret.o % exp_diff); + import std.stdio; + writeln(f); + writeln(t); + writeln((P(2) ^^ F.max_exp) * t); ret.o = f; - ret.put(E(2) ^^ t); + ret.put((P(2) ^^ F.max_exp) * t); } } + return ret; } } @@ -707,25 +559,26 @@ public: unittest { import std.math; - float M = 2.0f ^^ (float.max_exp-1); - double N = 2.0 ^^ (float.max_exp-1); - auto s = Summator(0.0f); //float summator + float M = 2.0f .pow (float.max_exp-1); + double N = 2.0 .pow (float.max_exp-1); + auto s = Summator!float(0.0f); //float summator s += M; s += M; - auto e = s.extendTo!double; + auto e = cast(Summator!double) s; - assert(M+M == s.sum); + import std.conv; + assert(M+M == s.sum, " " ~s.sum.to!string~" "~M.to!string); assert(M+M == float.infinity); - assert(N+N == e.sum); + assert(N+N == e.sum, e.sum.to!string~" "~N.to!string); assert(N+N != double.infinity); } /++ $(D cast(F)) operator overlaoding. Returns $(D cast(T)sum()). - See also: $(D extendTo) + See also: $(D cast) +/ - T opCast(T)() if(Unqual!T == F) + T opCast(T)() if (is(Unqual!T == F)) { return sum(); } @@ -736,7 +589,7 @@ public: partials.length = 0; s = 0; o = 0; - if(rhs) put(rhs); + if (rhs) put(rhs); } /// += and -= operator overlaoding. @@ -768,33 +621,32 @@ public: foreach(f; rhs.partials[]) put(-f); } - + import std.stdio, std.conv; /// unittest { import std.math, std.algorithm, std.range; - auto r1 = iota( 1, 501 ).map!(a => (-1.0)^^a/a); - auto r2 = iota(501, 1001).map!(a => (-1.0)^^a/a); - Summator!double s1 = 0.69264743055982025, s2 = 0; + auto r1 = iota( 1, 501 ).map!(a => (-1.0).pow(a)/a); + auto r2 = iota(501, 1001).map!(a => (-1.0).pow(a)/a); + Summator!double s1 = 0.0, s2 = 0.0; foreach(e; r1) s1 += e; foreach(e; r2) s2 -= e; s1 -= s2; - assert(s1.sum == 0); + assert(s1.sum == -0.69264743055982025); } ///Returns $(D true) if current sum is a NaN. bool isNaN() const { - if(s.isNaN) - return true; - if(s) - return (s + overflow).isNaN; - return false; + import std.stdio; + writeln(s); + writeln(overflow); + return .isNaN(s); } ///Returns $(D true) if current sum is finite (not infinite or NaN). bool isFinite() const { - if(s) + if (s) return false; return !overflow; } @@ -802,14 +654,16 @@ public: ///Returns $(D true) if current sum is ±∞. bool isInfinity() const { - if(s.isNaN) - return false; - return (s + overflow).isInfinity; + return .isInfinity(s) || overflow(); } } unittest { + import std.range; + import std.algorithm; + import std.math; + Summator!double summator = 0; enum double M = (cast(double)2) ^^ (double.max_exp - 1); @@ -870,11 +724,409 @@ unittest { foreach(t; test[0]) summator.put(t); auto r = test[1]; - assert(summator.isNaN == r.isNaN); - assert(summator.isFinite == r.isFinite); - assert(summator.isInfinity == r.isInfinity); + import std.conv; + assert(summator.isNaN() == r.isNaN(), summator.isNaN().to!string~" "~test[0].to!string); + assert(summator.isFinite() == r.isFinite()); + assert(summator.isInfinity() == r.isInfinity(), summator.isInfinity().to!string~" "~test[0].to!string); auto s = summator.sum; assert(s == r || s.isNaN && r.isNaN); summator = 0; } } + + +private: + +//template isComplex(C) +//{ +// import std.complex : Complex; +// enum isComplex = is(C : Complex!F, F); +//} + +// FIXME (perfomance issue): fabs in std.math avaliable only for for real. +F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter +{ + if (__ctfe) + { + return f < 0 ? -f : f; + } + else + { + version(LDC) + { + import ldc.intrinsics : llvm_fabs; + return llvm_fabs(f); + } + else + { + import core.stdc.tgmath : fabs; + return fabs(f); + } + } +} + +//if ( +// isInputRange!Range && +// isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && +// !isInfinite!Range && +// ( +// summation != Summation.Pairwise || +// hasLength!Range && hasSlicing!Range +// ) +//) + +/++ +Naive summation algorithm. ++/ +F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +{ + foreach(x; r) + { + s += x; + } + return s; +} + +///TODO +alias sumFast = sumNaive; + +/++ +$(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. ++/ + +F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r) +{ + import std.range : hasLength, hasSlicing; + static assert(hasLength!Range && hasSlicing!Range); + switch (r.length) + { + case 0: return F(0); + case 1: return cast(F)r[0]; + case 2: return cast(F)(r[0] + cast(F)r[1]); + default: auto n = r.length/2; return cast(F)(sumPairwise!(Range, F)(r[0..n]) + sumPairwise!(Range, F)(r[n..$])); + } +} + +F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed) +{ + return sumPairwise!Range(r) + seed; +} + + +/++ +$(LUCKY Kahan summation) algorithm. ++/ +/++ +--------------------- +s := x[1] +c := 0 +FOR k := 2 TO n DO +y := x[k] - c +t := s + y +c := (t - s) - y +s := t +END DO +--------------------- ++/ +F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +{ + F c = 0.0; + F y; // do not declare in the loop (algo can be used for matrixes and etc) + F t; // ditto + foreach(F x; r) + { + y = x - c; + t = s + y; + c = t - s; + c -= y; + s = t; + } + return s; +} + + +/++ +$(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). ++/ +/++ +--------------------- +s := x[1] +c := 0 +FOR i := 2 TO n DO +t := s + x[i] +IF ABS(s) >= ABS(x[i]) THEN + c := c + ((s-t)+x[i]) +ELSE + c := c + ((x[i]-t)+s) +END IF +s := t +END DO +s := s + c +--------------------- ++/ +F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +{ + F c = 0.0; + static if (isFloatingPoint!F) + { + foreach(F x; r) + { + F t = s + x; + if (fabs(s) >= fabs(x)) + c += (s-t)+x; + else + c += (x-t)+s; + s = t; + } + } + else + { + foreach(F x; r) + { + F t = s + x; + if (fabs(s.re) < fabs(x.re)) + { + auto t_re = s.re; + s.re = x.re; + x.re = t_re; + } + if (fabs(s.im) < fabs(x.im)) + { + auto t_im = s.im; + s.im = x.im; + x.im = t_im; + } + c += (s-t)+x; + s = t; + } + } + return s + c; +} + + +/++ +$(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). ++/ +/++ +--------------------- +s := 0 ; cs := 0 ; ccs := 0 +FOR j := 1 TO n DO + t := s + x[i] + IF ABS(s) >= ABS(x[i]) THEN + c := (s-t) + x[i] + ELSE + c := (x[i]-t) + s + END IF + s := t + t := cs + c + IF ABS(cs) >= ABS(c) THEN + cc := (cs-t) + c + ELSE + cc := (c-t) + cs + END IF + cs := t + ccs := ccs + cc +END FOR +RETURN s+cs+ccs +--------------------- ++/ +F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +{ + F cs = 0.0; + F ccs = 0.0; + static if (isFloatingPoint!F) + { + foreach(F x; r) + { + F t = s + x; + F c = void; + if (fabs(s) >= fabs(x)) + c = (s-t)+x; + else + c = (x-t)+s; + s = t; + t = cs + c; + if (fabs(cs) >= fabs(c)) + ccs += (cs-t)+c; + else + ccs += (c-t)+cs; + cs = t; + } + } + else + { + foreach(F x; r) + { + F t = s + x; + if (fabs(s.re) < fabs(x.re)) + { + auto t_re = s.re; + s.re = x.re; + x.re = t_re; + } + if (fabs(s.im) < fabs(x.im)) + { + auto t_im = s.im; + s.im = x.im; + x.im = t_im; + } + F c = (s-t)+x; + s = t; + if (fabs(cs.re) < fabs(c.re)) + { + auto t_re = cs.re; + cs.re = c.re; + c.re = t_re; + } + if (fabs(cs.im) < fabs(c.im)) + { + auto t_im = cs.im; + cs.im = c.im; + c.im = t_im; + } + ccs += (cs-t)+c; + cs = t; + } + } + return s+cs+ccs; // no rounding in between +} + + +unittest +{ + import std.typetuple; + foreach(I; TypeTuple!(byte, uint, long)) + { + I[] ar = [1, 2, 3, 4]; + I r = 10; + assert(r == ar.sumFast); + assert(r == ar.sumPairwise); + } +} + +unittest +{ + import std.typetuple; + foreach(F; TypeTuple!(float, double, real)) + { + F[] ar = [1, 2, 3, 4]; + F r = 10; + assert(r == ar.sumFast); + assert(r == ar.sumPairwise); + assert(r == ar.sumKahan); + assert(r == ar.sumKBN); + assert(r == ar.sumKB2); + } +} + +unittest +{ + import std.complex; + Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; + Complex!double r = complex(10, 14); + assert(r == ar.sumFast); + assert(r == ar.sumPairwise); + assert(r == ar.sumKahan); + assert(r == ar.sumKBN); + assert(r == ar.sumKB2); +} + +//BUG: DMD 2.066 Segmentation fault (core dumped) +//unittest +//{ +// import core.simd; +// static if (__traits(compiles, double2.init + double2.init)) +// { +// double2[] ar = [double2([1.0, 2]), double2([2, 3]), double2([3, 4]), double2([4, 6])]; +// assert(ar.sumFast.array == double2([10, 14]).array); +// assert(ar.sumPairwise.array == double2([10, 14]).array); +// assert(ar.sumKahan.array == double2([10, 14]).array); +// } +//} + +unittest +{ + import std.algorithm : map; + auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); + double r = 20000; + assert(r != ar.sumFast); + assert(r != ar.sumPairwise); + assert(r != ar.sumKahan); + assert(r == ar.sumKBN); + assert(r == ar.sumKB2); +} + +/++ +Precise summation. ++/ +F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0) +{ + static if (isFloatingPoint!F) + { + auto sum = Summator!F(seed); + foreach(e; r) + { + sum.put(e); + } + return sum.sum; + } + else + { + alias T = typeof(F.init.re); + static if (isForwardRange!Range) + { + auto s = r.save; + auto sum = Summator!T(seed.re); + foreach(e; r) + { + sum.put(e.re); + } + T sumRe = sum.sum; + sum = seed.im; + foreach(e; s) + { + sum.put(e.im); + } + return F(sumRe, sum.sum); + } + else + { + auto sumRe = Summator!T(seed.re); + auto sumIm = Summator!T(seed.im); + foreach(e; r) + { + sumRe.put(e.re); + sumIm.put(e.im); + } + return F(sumRe.sum, sumIm.sum); + } + } +} + +template Algo(Summation summation) +{ + + static if (summation == Summation.Fast) + alias Algo = sumFast; + else + static if (summation == Summation.Naive) + alias Algo = sumNaive; + else + static if (summation == Summation.Pairwise) + alias Algo = sumPairwise; + else + static if (summation == Summation.Kahan) + alias Algo = sumKahan; + else + static if (summation == Summation.KBN) + alias Algo = sumKBN; + else + static if (summation == Summation.KB2) + alias Algo = sumKB2; + else + static if (summation == Summation.Precise) + alias Algo = sumPrecise; + else + static assert(0); + +} From da3531049379b9e34124a1d501fc561ed41d113d Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:00:34 +0300 Subject: [PATCH 10/46] fix typo --- std/numeric/{pacakge.d => package.d} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename std/numeric/{pacakge.d => package.d} (100%) diff --git a/std/numeric/pacakge.d b/std/numeric/package.d similarity index 100% rename from std/numeric/pacakge.d rename to std/numeric/package.d From fbec67dff5f9d74f697a2790811826b5a904594b Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:04:17 +0300 Subject: [PATCH 11/46] fix typo --- win32.mak | 6 +++--- win64.mak | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/win32.mak b/win32.mak index 07d45ab027b..6b36956bf69 100644 --- a/win32.mak +++ b/win32.mak @@ -185,7 +185,7 @@ SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\pa SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ std\range\interfaces.d -SRC_STD_NUMERIC= std\numeric\pacakge.d std\numeric\summation.d +SRC_STD_NUMERIC= std\numeric\package.d std\numeric\summation.d SRC_STD_NET= std\net\isemail.d std\net\curl.d @@ -460,7 +460,7 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -cov=93 -unittest -main -run std\csv.d $(DMD) -cov=91 -unittest -main -run std\math.d $(DMD) -cov=95 -unittest -main -run std\complex.d - $(DMD) -cov=70 -unittest -main -run std\numeric\pacakge.d + $(DMD) -cov=70 -unittest -main -run std\numeric\package.d $(DMD) -cov=70 -unittest -main -run std\numeric\summation.d $(DMD) -cov=94 -unittest -main -run std\bigint.d $(DMD) -cov=95 -unittest -main -run std\bitmanip.d @@ -712,7 +712,7 @@ $(DOC)\std_mmfile.html : $(STDDOC) std\mmfile.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_mmfile.html $(STDDOC) std\mmfile.d $(DOC)\std_numeric.html : $(STDDOC) std\numeric\package.d - $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric_pacakge.html $(STDDOC) std\numeric\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric_package.html $(STDDOC) std\numeric\package.d $(DOC)\std_numeric_summation.html : $(STDDOC) std\numeric\summation.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_numeric_summation.html $(STDDOC) std\numeric\summation.d diff --git a/win64.mak b/win64.mak index 48adf7cb815..886e0401b61 100644 --- a/win64.mak +++ b/win64.mak @@ -202,7 +202,7 @@ SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\pa SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ std\range\interfaces.d -SRC_STD_NUMERIC= std\numeric\pacakge.d std\numeric\summation.d +SRC_STD_NUMERIC= std\numeric\package.d std\numeric\summation.d SRC_STD_NET= std\net\isemail.d std\net\curl.d From 86e86e33550e568ed197db200849b2eb28142910 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:06:21 +0300 Subject: [PATCH 12/46] fix spaces --- std/numeric/summation.d | 61 +++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 4c7307c7018..3a717d9ef9f 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -108,20 +108,20 @@ unittest /// + and - operator overloading Quaternion opBinary(string op)(auto ref Quaternion rhs) const - if (op == "+" || op == "-") + if (op == "+" || op == "-") { Quaternion ret = void; - foreach(i, ref e; ret.array) + foreach (i, ref e; ret.array) mixin("e = array[i] "~op~" rhs.array[i];"); return ret; } /// += and -= operator overloading Quaternion opOpAssign(string op)(auto ref Quaternion rhs) - if (op == "+" || op == "-") + if (op == "+" || op == "-") { Quaternion ret = void; - foreach(i, ref e; array) + foreach (i, ref e; array) mixin("e "~op~"= rhs.array[i];"); return this; } @@ -361,7 +361,7 @@ public: if (.isFinite(x)) { size_t i; - foreach(y; partials[]) + foreach (y; partials[]) { F h = x + y; if (.isInfinity(h)) @@ -414,7 +414,7 @@ public: void unsafePut(F x) { size_t i; - foreach(y; partials[]) + foreach (y; partials[]) { F h = x + y; debug(numeric) assert(h.isFinite); @@ -441,7 +441,7 @@ public: { debug(numeric) { - foreach(y; partials[]) + foreach (y; partials[]) { assert(y); assert(y.isFinite); @@ -515,10 +515,11 @@ public: //} ///Returns $(D Summator) with extended internal partial sums. - T opCast(T : Summator!P, P)() if ( - isMutable!T && - P.max_exp >= F.max_exp && - P.mant_dig >= F.mant_dig + T opCast(T : Summator!P, P)() + if ( + isMutable!T && + P.max_exp >= F.max_exp && + P.mant_dig >= F.mant_dig ) { static if (is(P == F)) @@ -529,7 +530,7 @@ public: ret.s = s; ret.o = o; ret.partials = scopeBuffer(ret.scopeBufferArray); - foreach(p; partials[]) + foreach (p; partials[]) { ret.partials.put(p); } @@ -603,7 +604,7 @@ public: { s += rhs.s; o += rhs.o; - foreach(f; rhs.partials[]) + foreach (f; rhs.partials[]) put(f); } @@ -618,7 +619,7 @@ public: { s -= rhs.s; o -= rhs.o; - foreach(f; rhs.partials[]) + foreach (f; rhs.partials[]) put(-f); } import std.stdio, std.conv; @@ -628,8 +629,8 @@ public: auto r1 = iota( 1, 501 ).map!(a => (-1.0).pow(a)/a); auto r2 = iota(501, 1001).map!(a => (-1.0).pow(a)/a); Summator!double s1 = 0.0, s2 = 0.0; - foreach(e; r1) s1 += e; - foreach(e; r2) s2 -= e; + foreach (e; r1) s1 += e; + foreach (e; r2) s2 -= e; s1 -= s2; assert(s1.sum == -0.69264743055982025); } @@ -720,9 +721,9 @@ unittest tuple([M, M, -1e307], 1.6976931348623159e+308), tuple([1e16, 1., 1e-16], 10000000000000002.0), ]; - foreach(test; tests) + foreach (test; tests) { - foreach(t; test[0]) summator.put(t); + foreach (t; test[0]) summator.put(t); auto r = test[1]; import std.conv; assert(summator.isNaN() == r.isNaN(), summator.isNaN().to!string~" "~test[0].to!string); @@ -780,7 +781,7 @@ Naive summation algorithm. +/ F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) { - foreach(x; r) + foreach (x; r) { s += x; } @@ -833,7 +834,7 @@ F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) F c = 0.0; F y; // do not declare in the loop (algo can be used for matrixes and etc) F t; // ditto - foreach(F x; r) + foreach (F x; r) { y = x - c; t = s + y; @@ -869,7 +870,7 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) F c = 0.0; static if (isFloatingPoint!F) { - foreach(F x; r) + foreach (F x; r) { F t = s + x; if (fabs(s) >= fabs(x)) @@ -881,7 +882,7 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) } else { - foreach(F x; r) + foreach (F x; r) { F t = s + x; if (fabs(s.re) < fabs(x.re)) @@ -936,7 +937,7 @@ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) F ccs = 0.0; static if (isFloatingPoint!F) { - foreach(F x; r) + foreach (F x; r) { F t = s + x; F c = void; @@ -955,7 +956,7 @@ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) } else { - foreach(F x; r) + foreach (F x; r) { F t = s + x; if (fabs(s.re) < fabs(x.re)) @@ -995,7 +996,7 @@ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) unittest { import std.typetuple; - foreach(I; TypeTuple!(byte, uint, long)) + foreach (I; TypeTuple!(byte, uint, long)) { I[] ar = [1, 2, 3, 4]; I r = 10; @@ -1007,7 +1008,7 @@ unittest unittest { import std.typetuple; - foreach(F; TypeTuple!(float, double, real)) + foreach (F; TypeTuple!(float, double, real)) { F[] ar = [1, 2, 3, 4]; F r = 10; @@ -1064,7 +1065,7 @@ F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0) static if (isFloatingPoint!F) { auto sum = Summator!F(seed); - foreach(e; r) + foreach (e; r) { sum.put(e); } @@ -1077,13 +1078,13 @@ F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0) { auto s = r.save; auto sum = Summator!T(seed.re); - foreach(e; r) + foreach (e; r) { sum.put(e.re); } T sumRe = sum.sum; sum = seed.im; - foreach(e; s) + foreach (e; s) { sum.put(e.im); } @@ -1093,7 +1094,7 @@ F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0) { auto sumRe = Summator!T(seed.re); auto sumIm = Summator!T(seed.im); - foreach(e; r) + foreach (e; r) { sumRe.put(e.re); sumIm.put(e.im); From dece67f805ce5b86910c98b80b3cf310cb658614 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:06:53 +0300 Subject: [PATCH 13/46] fix comments --- std/numeric/summation.d | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 3a717d9ef9f..960453851b0 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -9,9 +9,9 @@ import std.traits; import std.typecons; import std.range.primitives; -/** +/++ Computes sum of range. -*/ ++/ //TODO: CTFE for Precise. Needs CTFEScopeBuffer. template fsum(F, Summation summation = Summation.Precise) if (isFloatingPoint!F && isMutable!F) @@ -96,9 +96,9 @@ unittest { assert(r == ar.fsum); //Summation.Precise } -/** +/++ $(D Fast), $(D Pairwise) and $(D Kahan) algorithms can be used for summation user defined types. -*/ ++/ unittest { static struct Quaternion(F) @@ -143,18 +143,18 @@ unittest assert(r == [p, q].fsum!(Summation.Kahan)); } -/** +/++ Data type for summation can be specified. -*/ ++/ unittest { alias fs = fsum!double; // static assert(is(typeof([1, 3, 2].fs()) == double)); static assert(is(typeof([1.0,2.0].fsum!(float, Summation.KBN)()) == float), typeof([1.0,2.0].fsum!(float, Summation.KBN)()).stringof); } -/** +/++ All summation algorithms available for complex numbers. -*/ ++/ unittest { import std.complex; @@ -164,46 +164,46 @@ unittest } -/** +/++ Summation algorithms for ranges of floating point numbers or $(D Complex). -*/ ++/ enum Summation { - /** + /++ Fast summation algorithm. - */ + +/ Fast, - /** + /++ Naive algorithm. - */ + +/ Naive, - /** + /++ $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. - */ + +/ Pairwise, - /** + /++ $(LUCKY Kahan summation) algorithm. - */ + +/ Kahan, - /** + /++ $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). - */ + +/ KBN, - /** + /++ $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). - */ + +/ KB2, - /** + /++ Full precision summation algorithm. Returns the value of the sum, rounded to the nearest representable floating-point number using the $(LUCKY round-half-to-even rule). - */ + +/ Precise, } From b6fe9e4b40c3232f3e80393559c42ce71d2c247a Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:08:14 +0300 Subject: [PATCH 14/46] remove blank lines --- std/numeric/summation.d | 6 ------ 1 file changed, 6 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 960453851b0..31606b340bb 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -682,26 +682,20 @@ unittest tuple([2.0^^53+10.0, 1.0, 2.0^^-100], 2.0^^53+12.0), tuple([2.0^^53-4.0, 0.5, 2.0^^-54], 2.0^^53-3.0), tuple([M-2.0^^970, -1.0, M], 1.7976931348623157e+308), - tuple([double.max, double.max*2.^^-54], double.max), tuple([double.max, double.max*2.^^-53], double.infinity), - tuple(iota(1, 1001).map!(a => 1.0/a).array , 7.4854708605503451), tuple(iota(1, 1001).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... tuple(iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000))]).array , -1.0), - tuple(iota(1, 1001).map!(a => 1.0/a).retro.array , 7.4854708605503451), tuple(iota(1, 1001).map!(a => (-1.0)^^a/a).retro.array, -0.69264743055982025), tuple(iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000))]).retro.array , -1.0), - - tuple([double.infinity, -double.infinity, double.nan], double.nan), tuple([double.nan, double.infinity, -double.infinity], double.nan), tuple([double.infinity, double.nan, double.infinity], double.nan), tuple([double.infinity, double.infinity], double.infinity), tuple([double.infinity, -double.infinity], double.nan), tuple([-double.infinity, 1e308, 1e308, -double.infinity], -double.infinity), - tuple([M-2.0^^970, 0.0, M], double.infinity), tuple([M-2.0^^970, 1.0, M], double.infinity), tuple([M, M], double.infinity), From e888182044b1b373b7cdd3a3abd240550079a33a Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:10:32 +0300 Subject: [PATCH 15/46] remove debug info --- std/numeric/summation.d | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 31606b340bb..0b4c1248c94 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -534,9 +534,6 @@ public: { ret.partials.put(p); } - //pragma(msg, P.max_exp); - //pragma(msg, F.max_exp); - pragma(msg, P.max_exp / F.max_exp); enum exp_diff = P.max_exp / F.max_exp; static if (exp_diff) { @@ -544,10 +541,6 @@ public: { immutable f = ret.o / exp_diff; immutable t = cast(int)(ret.o % exp_diff); - import std.stdio; - writeln(f); - writeln(t); - writeln((P(2) ^^ F.max_exp) * t); ret.o = f; ret.put((P(2) ^^ F.max_exp) * t); } @@ -622,7 +615,6 @@ public: foreach (f; rhs.partials[]) put(-f); } - import std.stdio, std.conv; /// unittest { import std.math, std.algorithm, std.range; @@ -638,9 +630,6 @@ public: ///Returns $(D true) if current sum is a NaN. bool isNaN() const { - import std.stdio; - writeln(s); - writeln(overflow); return .isNaN(s); } @@ -719,10 +708,9 @@ unittest { foreach (t; test[0]) summator.put(t); auto r = test[1]; - import std.conv; - assert(summator.isNaN() == r.isNaN(), summator.isNaN().to!string~" "~test[0].to!string); + assert(summator.isNaN() == r.isNaN()); assert(summator.isFinite() == r.isFinite()); - assert(summator.isInfinity() == r.isInfinity(), summator.isInfinity().to!string~" "~test[0].to!string); + assert(summator.isInfinity() == r.isInfinity()); auto s = summator.sum; assert(s == r || s.isNaN && r.isNaN); summator = 0; From d48baf6037d98d632b5b6b062b9b24685605ffe1 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:17:15 +0300 Subject: [PATCH 16/46] style fix --- std/numeric/summation.d | 65 +++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 0b4c1248c94..acd0079a561 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -829,7 +829,8 @@ F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) /++ -$(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). +$(LUCKY Kahan-Babuška-Neumaier summation algorithm). +$(D КBN) gives more accurate results then $(D Kahan). +/ /++ --------------------- @@ -888,7 +889,8 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) /++ -$(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). +$(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. +$(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). +/ /++ --------------------- @@ -982,8 +984,8 @@ unittest { I[] ar = [1, 2, 3, 4]; I r = 10; - assert(r == ar.sumFast); - assert(r == ar.sumPairwise); + assert(r == ar.sumFast()); + assert(r == ar.sumPairwise()); } } @@ -994,11 +996,11 @@ unittest { F[] ar = [1, 2, 3, 4]; F r = 10; - assert(r == ar.sumFast); - assert(r == ar.sumPairwise); - assert(r == ar.sumKahan); - assert(r == ar.sumKBN); - assert(r == ar.sumKB2); + assert(r == ar.sumFast()); + assert(r == ar.sumPairwise()); + assert(r == ar.sumKahan()); + assert(r == ar.sumKBN()); + assert(r == ar.sumKB2()); } } @@ -1007,36 +1009,37 @@ unittest import std.complex; Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; Complex!double r = complex(10, 14); - assert(r == ar.sumFast); - assert(r == ar.sumPairwise); - assert(r == ar.sumKahan); - assert(r == ar.sumKBN); - assert(r == ar.sumKB2); + assert(r == ar.sumFast()); + assert(r == ar.sumPairwise()); + assert(r == ar.sumKahan()); + assert(r == ar.sumKBN()); + assert(r == ar.sumKB2()); } -//BUG: DMD 2.066 Segmentation fault (core dumped) -//unittest -//{ -// import core.simd; -// static if (__traits(compiles, double2.init + double2.init)) -// { -// double2[] ar = [double2([1.0, 2]), double2([2, 3]), double2([3, 4]), double2([4, 6])]; -// assert(ar.sumFast.array == double2([10, 14]).array); -// assert(ar.sumPairwise.array == double2([10, 14]).array); -// assert(ar.sumKahan.array == double2([10, 14]).array); -// } -//} +//@@@BUG@@@: DMD 2.066 Segmentation fault (core dumped) +version(none) +unittest +{ + import core.simd; + static if (__traits(compiles, double2.init + double2.init)) + { + double2[] ar = [double2([1.0, 2]), double2([2, 3]), double2([3, 4]), double2([4, 6])]; + assert(ar.sumFast().array == double2([10, 14]).array); + assert(ar.sumPairwise().array == double2([10, 14]).array); + assert(ar.sumKahan().array == double2([10, 14]).array); + } +} unittest { import std.algorithm : map; auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); double r = 20000; - assert(r != ar.sumFast); - assert(r != ar.sumPairwise); - assert(r != ar.sumKahan); - assert(r == ar.sumKBN); - assert(r == ar.sumKB2); + assert(r != ar.sumFast()); + assert(r != ar.sumPairwise()); + assert(r != ar.sumKahan()); + assert(r == ar.sumKBN()); + assert(r == ar.sumKB2()); } /++ From 0bb19a607b003649405778442989f8c1bd054ead Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:32:32 +0300 Subject: [PATCH 17/46] minor fixes --- std/numeric/summation.d | 155 +++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 80 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index acd0079a561..b5a2c3db285 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -8,22 +8,67 @@ module std.numeric.summation; import std.traits; import std.typecons; import std.range.primitives; +import std.math : isNaN, isFinite, isInfinity, signbit; + +/++ +Summation algorithms for ranges of floating point numbers or $(D Complex). ++/ +enum Summation +{ + /++ + Fast summation algorithm. + +/ + Fast, + + /++ + Naive algorithm. + +/ + Naive, + + /++ + $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. + +/ + Pairwise, + + /++ + $(LUCKY Kahan summation) algorithm. + +/ + Kahan, + + /++ + $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). + +/ + KBN, + + /++ + $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). + +/ + KB2, + + /++ + Precise summation algorithm. + Returns the value of the sum, rounded to the nearest representable + floating-point number using the $(LUCKY round-half-to-even rule). + +/ + Precise, +} /++ Computes sum of range. +/ -//TODO: CTFE for Precise. Needs CTFEScopeBuffer. template fsum(F, Summation summation = Summation.Precise) if (isFloatingPoint!F && isMutable!F) { alias sum = Algo!summation; F fsum(Range)(Range r) + if (isInputRange!Range) { return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) + if (isInputRange!Range) { return sum!(Range, F)(r, seed); } @@ -59,11 +104,13 @@ template fsum(Summation summation = Summation.Precise) alias sum = Algo!summation; Unqual!(ForeachType!Range) fsum(Range)(Range r) + if (isInputRange!Range) { return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) + if (isInputRange!Range) { return sum!(F, Range)(r, seed); } @@ -143,14 +190,6 @@ unittest assert(r == [p, q].fsum!(Summation.Kahan)); } -/++ -Data type for summation can be specified. -+/ -unittest { - alias fs = fsum!double; -// static assert(is(typeof([1, 3, 2].fs()) == double)); - static assert(is(typeof([1.0,2.0].fsum!(float, Summation.KBN)()) == float), typeof([1.0,2.0].fsum!(float, Summation.KBN)()).stringof); -} /++ All summation algorithms available for complex numbers. @@ -164,50 +203,6 @@ unittest } -/++ -Summation algorithms for ranges of floating point numbers or $(D Complex). -+/ -enum Summation -{ - /++ - Fast summation algorithm. - +/ - Fast, - - /++ - Naive algorithm. - +/ - Naive, - - /++ - $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. - +/ - Pairwise, - - /++ - $(LUCKY Kahan summation) algorithm. - +/ - Kahan, - - /++ - $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). - +/ - KBN, - - /++ - $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). - +/ - KB2, - - /++ - Full precision summation algorithm. - Returns the value of the sum, rounded to the nearest representable - floating-point number using the $(LUCKY round-half-to-even rule). - +/ - Precise, -} - - /++ Handler for full precise summation with $(D put) primitive. The current implementation re-establish special @@ -224,8 +219,6 @@ Dickinson's post at . See those links for more details, proofs and other references. IEEE 754R floating point semantics are assumed. +/ - import std.math : isNaN, isFinite, isInfinity, signbit; - struct Summator(F) if (isFloatingPoint!F && isMutable!F) { @@ -412,14 +405,17 @@ public: Adds $(D x) to the internal partial sums. +/ void unsafePut(F x) - { + in { + assert(.isFinite(x)); + } + body { size_t i; foreach (y; partials[]) { F h = x + y; - debug(numeric) assert(h.isFinite); + debug(numeric) assert(.isFinite(h)); F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); - debug(numeric) assert(l.isFinite); + debug(numeric) assert(.isFinite(l)); if (l) { partials[i++] = l; @@ -498,21 +494,20 @@ public: return partialsReduce(y, parts); } - ///++ - //+/ - //F partialsSum() const - //{ - // debug(numeric) partialsDebug; - // auto parts = partials[]; - // F y = 0; - // //pick last - // if (parts.length) - // { - // y = parts[$-1]; - // parts = parts[0..$-1]; - // } - // return partialsReduce(y, parts); - //} + version(none) + F partialsSum() const + { + debug(numeric) partialsDebug; + auto parts = partials[]; + F y = 0; + //pick last + if (parts.length) + { + y = parts[$-1]; + parts = parts[0..$-1]; + } + return partialsReduce(y, parts); + } ///Returns $(D Summator) with extended internal partial sums. T opCast(T : Summator!P, P)() @@ -553,18 +548,16 @@ public: unittest { import std.math; - float M = 2.0f .pow (float.max_exp-1); - double N = 2.0 .pow (float.max_exp-1); - auto s = Summator!float(0.0f); //float summator + float M = 2.0f ^^ (float.max_exp-1); + double N = 2.0 ^^ (float.max_exp-1); + auto s = Summator!float(0); //float summator s += M; s += M; auto e = cast(Summator!double) s; - import std.conv; - assert(M+M == s.sum, " " ~s.sum.to!string~" "~M.to!string); + assert(M+M == s.sum); assert(M+M == float.infinity); - - assert(N+N == e.sum, e.sum.to!string~" "~N.to!string); + assert(N+N == e.sum); assert(N+N != double.infinity); } @@ -778,6 +771,7 @@ $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. +/ F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r) + if (hasLength!Range && hasSlicing!Range) { import std.range : hasLength, hasSlicing; static assert(hasLength!Range && hasSlicing!Range); @@ -1036,6 +1030,7 @@ unittest auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); double r = 20000; assert(r != ar.sumFast()); + //assert(r != ar.sumNaive()); //undefined assert(r != ar.sumPairwise()); assert(r != ar.sumKahan()); assert(r == ar.sumKBN()); From bbfc8ab3010ec29b82f246b199fd16122d101647 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:46:55 +0300 Subject: [PATCH 18/46] move sumOfLog2s --- std/numeric/package.d | 41 ++---------------------------------- std/numeric/summation.d | 46 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/std/numeric/package.d b/std/numeric/package.d index 7adc2362abd..f686f7dd724 100644 --- a/std/numeric/package.d +++ b/std/numeric/package.d @@ -23,6 +23,8 @@ Distributed under the Boost Software License, Version 1.0. */ module std.numeric; +public import std.numeric.summation; + import std.complex; import std.exception; import std.math; @@ -1647,45 +1649,6 @@ unittest assert(a == [ 0.5, 0.5 ]); } -/** -Computes accurate sum of binary logarithms of input range $(D r). - */ -ElementType!Range sumOfLog2s(Range)(Range r) - if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) -{ - long exp = 0; - Unqual!(typeof(return)) x = 1; - foreach (e; r) - { - if (e < 0) - return typeof(return).nan; - int lexp = void; - x *= frexp(e, lexp); - exp += lexp; - if (x < 0.5) - { - x *= 2; - exp--; - } - } - return exp + log2(x); -} - -/// -unittest -{ - assert(sumOfLog2s(new double[0]) == 0); - assert(sumOfLog2s([0.0L]) == -real.infinity); - assert(sumOfLog2s([-0.0L]) == -real.infinity); - assert(sumOfLog2s([2.0L]) == 1); - assert(sumOfLog2s([-2.0L]).isNaN()); - assert(sumOfLog2s([real.nan]).isNaN()); - assert(sumOfLog2s([-real.nan]).isNaN()); - assert(sumOfLog2s([real.infinity]) == real.infinity); - assert(sumOfLog2s([-real.infinity]).isNaN()); - assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); -} - /** Computes $(LUCKY _entropy) of input range $(D r) in bits. This function assumes (without checking) that the values in $(D r) are all diff --git a/std/numeric/summation.d b/std/numeric/summation.d index b5a2c3db285..3e59e4e0e71 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -1,14 +1,54 @@ /++ + License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Ilya Yaroshenko - + Source: $(PHOBOSSRC std/internal/math/_summation.d) + + Source: $(PHOBOSSRC std/numeric/_summation.d) +/ module std.numeric.summation; import std.traits; import std.typecons; import std.range.primitives; -import std.math : isNaN, isFinite, isInfinity, signbit; +import std.math : isNaN, isFinite, isInfinity, signbit, frexp; + + +/++ +Computes accurate sum of binary logarithms of input range $(D r). ++/ +ElementType!Range sumOfLog2s(Range)(Range r) + if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) +{ + long exp = 0; + Unqual!(typeof(return)) x = 1; + foreach (e; r) + { + if (e < 0) + return typeof(return).nan; + int lexp = void; + x *= frexp(e, lexp); + exp += lexp; + if (x < 0.5) + { + x *= 2; + exp--; + } + } + return exp + log2(x); +} + +/// +unittest +{ + assert(sumOfLog2s(new double[0]) == 0); + assert(sumOfLog2s([0.0L]) == -real.infinity); + assert(sumOfLog2s([-0.0L]) == -real.infinity); + assert(sumOfLog2s([2.0L]) == 1); + assert(sumOfLog2s([-2.0L]).isNaN()); + assert(sumOfLog2s([real.nan]).isNaN()); + assert(sumOfLog2s([-real.nan]).isNaN()); + assert(sumOfLog2s([real.infinity]) == real.infinity); + assert(sumOfLog2s([-real.infinity]).isNaN()); + assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); +} /++ Summation algorithms for ranges of floating point numbers or $(D Complex). @@ -1086,7 +1126,6 @@ F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0) template Algo(Summation summation) { - static if (summation == Summation.Fast) alias Algo = sumFast; else @@ -1109,5 +1148,4 @@ template Algo(Summation summation) alias Algo = sumPrecise; else static assert(0); - } From ed825696aa89e37aa37c7f43897bdd0b4f677b79 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 01:57:17 +0300 Subject: [PATCH 19/46] update headers --- std/numeric/package.d | 2 +- std/numeric/summation.d | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/std/numeric/package.d b/std/numeric/package.d index f686f7dd724..5415b9e2147 100644 --- a/std/numeric/package.d +++ b/std/numeric/package.d @@ -12,7 +12,7 @@ WIKI = Phobos/StdNumeric Copyright: Copyright Andrei Alexandrescu 2008 - 2009. License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(WEB erdani.org, Andrei Alexandrescu), - Don Clugston, Robert Jacques, Ilya Yaroshenko (summation) + Don Clugston, Robert Jacques Source: $(PHOBOSSRC std/_numeric.d) */ /* diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 3e59e4e0e71..399c41c04f7 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -1,8 +1,12 @@ -/++ - + License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). - + Authors: Ilya Yaroshenko - + Source: $(PHOBOSSRC std/numeric/_summation.d) - +/ +/** +This module contains basic summation algorithms. + +License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(WEB 9il.github.io, Ilya Yaroshenko) + +Source: $(PHOBOSSRC std/numeric/_summation.d) +*/ module std.numeric.summation; import std.traits; @@ -809,7 +813,6 @@ alias sumFast = sumNaive; /++ $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. +/ - F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r) if (hasLength!Range && hasSlicing!Range) { From ad0cb3d220551a46e30925d074d46847c33a9732 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 08:57:41 +0300 Subject: [PATCH 20/46] fix std.math imports --- std/numeric/package.d | 3 +++ std/numeric/summation.d | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/std/numeric/package.d b/std/numeric/package.d index 5415b9e2147..32b77b9f2b7 100644 --- a/std/numeric/package.d +++ b/std/numeric/package.d @@ -814,6 +814,7 @@ T findRoot(T, DF, DT)(scope DF f, in T a, in T b, is(typeof(f(T.init)) == R, R) && isFloatingPoint!R ) { + import std.math : fabs; // FIXME auto r = findRoot(f, a, b, f(a), f(b), tolerance); // Return the first value if it is smaller or NaN return !(fabs(r[2]) > fabs(r[3])) ? r[0] : r[1]; @@ -872,6 +873,8 @@ in } body { + import std.math : fabs; // FIXME + // Author: Don Clugston. This code is (heavily) modified from TOMS748 // (www.netlib.org). The changes to improve the worst-cast performance are // entirely original. diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 399c41c04f7..fc2dd885cbf 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -21,6 +21,7 @@ Computes accurate sum of binary logarithms of input range $(D r). ElementType!Range sumOfLog2s(Range)(Range r) if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) { + import std.math : frexp, log2; long exp = 0; Unqual!(typeof(return)) x = 1; foreach (e; r) @@ -599,9 +600,9 @@ public: s += M; auto e = cast(Summator!double) s; - assert(M+M == s.sum); + assert(M+M == s.sum()); assert(M+M == float.infinity); - assert(N+N == e.sum); + assert(N+N == e.sum()); assert(N+N != double.infinity); } @@ -661,7 +662,7 @@ public: foreach (e; r1) s1 += e; foreach (e; r2) s2 -= e; s1 -= s2; - assert(s1.sum == -0.69264743055982025); + assert(s1.sum() == -0.69264743055982025); } ///Returns $(D true) if current sum is a NaN. From bf7d32f2fb882b1233630e76b930f47aafbc7f1b Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 13:39:16 +0300 Subject: [PATCH 21/46] update docs --- std/numeric/summation.d | 254 ++++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 137 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index fc2dd885cbf..60b81c87593 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -61,12 +61,12 @@ Summation algorithms for ranges of floating point numbers or $(D Complex). enum Summation { /++ - Fast summation algorithm. + Fast summation algorithm. +/ Fast, /++ - Naive algorithm. + Naive algorithm (one by one). +/ Naive, @@ -74,22 +74,22 @@ enum Summation $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. +/ Pairwise, - + /++ $(LUCKY Kahan summation) algorithm. +/ Kahan, - + /++ $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). +/ KBN, - + /++ $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). +/ KB2, - + /++ Precise summation algorithm. Returns the value of the sum, rounded to the nearest representable @@ -107,62 +107,49 @@ template fsum(F, Summation summation = Summation.Precise) alias sum = Algo!summation; F fsum(Range)(Range r) - if (isInputRange!Range) + if (isSummable!(Range, F)) { return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) - if (isInputRange!Range) + if (isSummable!(Range, F)) { return sum!(Range, F)(r, seed); } //F fsum(Range)(Range r, F seed) // if ( - // isInputRange!Range && + // isInputRange!Range && // isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && // !isInfinite!Range && // ( - // summation != Summation.Pairwise || + // summation != Summation.Pairwise || // hasLength!Range && hasSlicing!Range // ) // ) } - //static assert( - // __traits(compiles, - // { - // F a = 0.0, b, c; - // c = a + b; - // c = a - b; - // static if (summation != Summation.Pairwise) - // { - // a += b; - // a -= b; - // } - // }), summation.stringof ~ " isn't implemented for " ~ F.stringof); - ///ditto template fsum(Summation summation = Summation.Precise) { alias sum = Algo!summation; Unqual!(ForeachType!Range) fsum(Range)(Range r) - if (isInputRange!Range) + if (isSummable!(Range, Unqual!(ForeachType!Range))) { return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) - if (isInputRange!Range) + if (isSummable!(Range, F)) { return sum!(F, Range)(r, seed); } } /// -unittest +unittest { import std.math, std.algorithm, std.range; auto ar = 1000 @@ -172,7 +159,7 @@ unittest //Summation.Precise is default assert(ar.fsum == -1.0); - assert(ar.retro.fsum == -1.0); + assert(ar.retro.fsum!real == -1.0); } /// @@ -189,11 +176,11 @@ unittest { } /++ -$(D Fast), $(D Pairwise) and $(D Kahan) algorithms can be used for summation user defined types. +$(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for user defined types. +/ -unittest +unittest { - static struct Quaternion(F) + static struct Quaternion(F) if (isFloatingPoint!F) { F[3] array; @@ -219,7 +206,7 @@ unittest } ///constructor with single FP argument - this(F f) + this(F f) { array[] = f; } @@ -230,21 +217,26 @@ unittest p.array = [3, 4, 5]; r.array = [3, 5, 7]; - assert(r == [p, q].fsum!(Summation.Fast)); + assert(r == [p, q].fsum!(Summation.Naive)); assert(r == [p, q].fsum!(Summation.Pairwise)); assert(r == [p, q].fsum!(Summation.Kahan)); } - /++ All summation algorithms available for complex numbers. +/ -unittest +unittest { import std.complex; Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; Complex!double r = complex(10, 14); - assert(r == ar.fsum); + assert(r == ar.fsum!(Summation.Fast)); + assert(r == ar.fsum!(Summation.Naive)); + assert(r == ar.fsum!(Summation.Pairwise)); + assert(r == ar.fsum!(Summation.Kahan)); + assert(r == ar.fsum!(Summation.KBN)); + assert(r == ar.fsum!(Summation.KB2)); + assert(r == ar.fsum); //Summation.Precise } @@ -254,7 +246,8 @@ The current implementation re-establish special value semantics across iterations (i.e. handling -inf + inf). References: $(LINK2 http://www.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps, -"Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates", Jonathan Richard Shewchuk) + "Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates", Jonathan Richard Shewchuk), + $(LINK2 http://bugs.python.org/file10357/msum4.py, Mark Dickinson's post at bugs.python.org). +/ /+ Precise summation function as msum() by Raymond Hettinger in @@ -272,11 +265,11 @@ struct Summator(F) private: enum F M = (cast(F)(2)) ^^ (F.max_exp - 1); F[32] scopeBufferArray = void; - ScopeBuffer!F partials; + ScopeBuffer!F partials; //sum for NaN and infinity. F s; //Overflow Degree. Count of 2^^F.max_exp minus count of -(2^^F.max_exp) - sizediff_t o; + sizediff_t o; /++ @@ -298,7 +291,7 @@ private: body { bool _break = void; - foreach_reverse(i, y; partials) + foreach_reverse(i, y; partials) { s = partialsReducePred(s, y, i ? partials[i-1] : 0, _break); if (_break) @@ -393,7 +386,7 @@ public: } } - ///Adds $(D x) to internal partial sums. + ///Adds $(D x) to the internal partial sums. void put(F x) { if (.isFinite(x)) @@ -409,14 +402,14 @@ public: F t = x; x = y; y = t; } //h == -F.infinity - if (signbit(h)) + if (signbit(h)) { x += M; x += M; o--; } //h == +F.infinity - else + else { x -= M; x -= M; @@ -448,8 +441,11 @@ public: /++ Adds $(D x) to the internal partial sums. + This operation doesn't re-establish special + value semantics across iterations (i.e. handling -inf + inf). + Preconditions: $(D isFinite(x)). +/ - void unsafePut(F x) + package void unsafePut(F x) in { assert(.isFinite(x)); } @@ -474,8 +470,18 @@ public: } } + /// + unittest { + import std.math, std.algorithm, std.range; + auto r = iota(1, 1001).map!(a => (-1.0).pow(a)/a); + Summator!double s = 0.0; + foreach(e; r) + s.unsafePut(e); + assert(s.sum() == -0.69264743055982025); + } + /++ - Returns the value of the sum, rounded to the nearest representable + Returns the value of the sum, rounded to the nearest representable floating-point number using the round-half-to-even rule. +/ F sum() const @@ -517,8 +523,8 @@ public: { // overflow, except in edge case... x = h + l; - y = parts.length && x - h == l && !signbit(l*parts[$-1]) ? - x * 2 : + y = parts.length && x - h == l && !signbit(l*parts[$-1]) ? + x * 2 : F.infinity * of; parts = null; } @@ -540,7 +546,7 @@ public: } version(none) - F partialsSum() const + F partialsSum() const { debug(numeric) partialsDebug; auto parts = partials[]; @@ -555,7 +561,7 @@ public: } ///Returns $(D Summator) with extended internal partial sums. - T opCast(T : Summator!P, P)() + T opCast(T : Summator!P, P)() if ( isMutable!T && P.max_exp >= F.max_exp && @@ -594,20 +600,20 @@ public: { import std.math; float M = 2.0f ^^ (float.max_exp-1); - double N = 2.0 ^^ (float.max_exp-1); auto s = Summator!float(0); //float summator s += M; s += M; - auto e = cast(Summator!double) s; - assert(M+M == s.sum()); - assert(M+M == float.infinity); + assert(M+M == float.infinity); + + double N = 2.0 ^^ (float.max_exp-1); + auto e = cast(Summator!double) s; assert(N+N == e.sum()); - assert(N+N != double.infinity); + assert(isFinite(N+N)); } /++ - $(D cast(F)) operator overlaoding. Returns $(D cast(T)sum()). + $(D cast(F)) operator overloading. Returns $(D cast(T)sum()). See also: $(D cast) +/ T opCast(T)() if (is(Unqual!T == F)) @@ -615,7 +621,7 @@ public: return sum(); } - ///The assignment operator $(D =) overlaoding. + ///The assignment operator $(D =) overloading. void opAssign(F rhs) { partials.length = 0; @@ -624,7 +630,7 @@ public: if (rhs) put(rhs); } - /// += and -= operator overlaoding. + /// $(D +=) and $(D -=) operator overloading. void opOpAssign(string op : "+")(F f) { put(f); @@ -653,14 +659,15 @@ public: foreach (f; rhs.partials[]) put(-f); } + /// unittest { import std.math, std.algorithm, std.range; auto r1 = iota( 1, 501 ).map!(a => (-1.0).pow(a)/a); auto r2 = iota(501, 1001).map!(a => (-1.0).pow(a)/a); Summator!double s1 = 0.0, s2 = 0.0; - foreach (e; r1) s1 += e; - foreach (e; r2) s2 -= e; + foreach (e; r1) s1 += e; + foreach (e; r2) s2 -= e; s1 -= s2; assert(s1.sum() == -0.69264743055982025); } @@ -686,14 +693,14 @@ public: } } -unittest +unittest { import std.range; import std.algorithm; import std.math; Summator!double summator = 0; - + enum double M = (cast(double)2) ^^ (double.max_exp - 1); Tuple!(double[], double)[] tests = [ tuple(new double[0], 0.0), @@ -758,13 +765,13 @@ unittest private: -//template isComplex(C) -//{ -// import std.complex : Complex; -// enum isComplex = is(C : Complex!F, F); -//} +template isComplex(C) +{ + import std.complex : Complex; + enum bool isComplex = is(C : Complex!F, F); +} -// FIXME (perfomance issue): fabs in std.math avaliable only for for real. +// FIXME (perfomance issue): fabs in std.math available only for for real. F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter { if (__ctfe) @@ -773,7 +780,7 @@ F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter } else { - version(LDC) + version(LDC) { import ldc.intrinsics : llvm_fabs; return llvm_fabs(f); @@ -786,20 +793,26 @@ F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter } } -//if ( -// isInputRange!Range && -// isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && -// !isInfinite!Range && -// ( -// summation != Summation.Pairwise || -// hasLength!Range && hasSlicing!Range -// ) -//) +template isSummable(Range, F) +{ + enum bool isSummable = + isInputRange!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && + !isInfinite!Range && + __traits(compiles, + { + F a = 0.1, b, c; + c = a + b; + c = a - b; + a += b; + a -= b; + }); +} /++ -Naive summation algorithm. +Naive summation algorithm. +/ -F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) { foreach (x; r) { @@ -849,7 +862,7 @@ s := t END DO --------------------- +/ -F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) { F c = 0.0; F y; // do not declare in the loop (algo can be used for matrixes and etc) @@ -862,12 +875,12 @@ F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) c -= y; s = t; } - return s; + return s; } /++ -$(LUCKY Kahan-Babuška-Neumaier summation algorithm). +$(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). +/ /++ @@ -886,7 +899,8 @@ END DO s := s + c --------------------- +/ -F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) + if (isFloatingPoint!F || isComplex!F) { F c = 0.0; static if (isFloatingPoint!F) @@ -923,11 +937,11 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) } } return s + c; -} +} /++ -$(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. +$(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). +/ /++ @@ -953,7 +967,8 @@ END FOR RETURN s+cs+ccs --------------------- +/ -F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) +F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) + if (isFloatingPoint!F || isComplex!F) { F cs = 0.0; F ccs = 0.0; @@ -1012,51 +1027,27 @@ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) } } return s+cs+ccs; // no rounding in between -} - - -unittest -{ - import std.typetuple; - foreach (I; TypeTuple!(byte, uint, long)) - { - I[] ar = [1, 2, 3, 4]; - I r = 10; - assert(r == ar.sumFast()); - assert(r == ar.sumPairwise()); - } } -unittest +unittest { import std.typetuple; + with(Summation) foreach (F; TypeTuple!(float, double, real)) { F[] ar = [1, 2, 3, 4]; F r = 10; - assert(r == ar.sumFast()); - assert(r == ar.sumPairwise()); - assert(r == ar.sumKahan()); - assert(r == ar.sumKBN()); - assert(r == ar.sumKB2()); + assert(r == ar.fsum!Fast()); + assert(r == ar.fsum!Pairwise()); + assert(r == ar.fsum!Kahan()); + assert(r == ar.fsum!KBN()); + assert(r == ar.fsum!KB2()); } } -unittest -{ - import std.complex; - Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; - Complex!double r = complex(10, 14); - assert(r == ar.sumFast()); - assert(r == ar.sumPairwise()); - assert(r == ar.sumKahan()); - assert(r == ar.sumKBN()); - assert(r == ar.sumKB2()); -} - //@@@BUG@@@: DMD 2.066 Segmentation fault (core dumped) version(none) -unittest +unittest { import core.simd; static if (__traits(compiles, double2.init + double2.init)) @@ -1068,23 +1059,12 @@ unittest } } -unittest -{ - import std.algorithm : map; - auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); - double r = 20000; - assert(r != ar.sumFast()); - //assert(r != ar.sumNaive()); //undefined - assert(r != ar.sumPairwise()); - assert(r != ar.sumKahan()); - assert(r == ar.sumKBN()); - assert(r == ar.sumKB2()); -} /++ Precise summation. +/ -F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0) +F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0.0) + if (isFloatingPoint!F || isComplex!F) { static if (isFloatingPoint!F) { @@ -1132,24 +1112,24 @@ template Algo(Summation summation) { static if (summation == Summation.Fast) alias Algo = sumFast; - else + else static if (summation == Summation.Naive) alias Algo = sumNaive; - else + else static if (summation == Summation.Pairwise) alias Algo = sumPairwise; - else + else static if (summation == Summation.Kahan) alias Algo = sumKahan; - else + else static if (summation == Summation.KBN) alias Algo = sumKBN; - else + else static if (summation == Summation.KB2) alias Algo = sumKB2; - else + else static if (summation == Summation.Precise) alias Algo = sumPrecise; - else + else static assert(0); } From 3d7dd5b9f5c8e3c80c6718fa930aab47ebde0062 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 13:51:31 +0300 Subject: [PATCH 22/46] remove spaces --- std/numeric/summation.d | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 60b81c87593..0b862514b54 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -55,8 +55,9 @@ unittest assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); } + /++ -Summation algorithms for ranges of floating point numbers or $(D Complex). +Summation algorithms. +/ enum Summation { @@ -74,30 +75,31 @@ enum Summation $(LUCKY Pairwise summation) algorithm. Range must be a finite sliceable range. +/ Pairwise, - + /++ $(LUCKY Kahan summation) algorithm. +/ Kahan, - + /++ $(LUCKY Kahan-Babuška-Neumaier summation algorithm). $(D КBN) gives more accurate results then $(D Kahan). +/ KBN, - + /++ $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. $(D КB2) gives more accurate results then $(D Kahan) and $(D КBN). +/ KB2, - + /++ Precise summation algorithm. - Returns the value of the sum, rounded to the nearest representable + The value of the sum is rounded to the nearest representable floating-point number using the $(LUCKY round-half-to-even rule). +/ Precise, } + /++ Computes sum of range. +/ @@ -265,7 +267,7 @@ struct Summator(F) private: enum F M = (cast(F)(2)) ^^ (F.max_exp - 1); F[32] scopeBufferArray = void; - ScopeBuffer!F partials; + ScopeBuffer!F partials; //sum for NaN and infinity. F s; //Overflow Degree. Count of 2^^F.max_exp minus count of -(2^^F.max_exp) @@ -700,7 +702,7 @@ unittest import std.math; Summator!double summator = 0; - + enum double M = (cast(double)2) ^^ (double.max_exp - 1); Tuple!(double[], double)[] tests = [ tuple(new double[0], 0.0), @@ -875,7 +877,7 @@ F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) c -= y; s = t; } - return s; + return s; } @@ -937,7 +939,7 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) } } return s + c; -} +} /++ From 28c52b125552256a1ef8ffc0763f0407ea318a19 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 21:46:25 +0300 Subject: [PATCH 23/46] remove commented code --- std/numeric/summation.d | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 0b862514b54..28db77b033c 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -119,17 +119,6 @@ template fsum(F, Summation summation = Summation.Precise) { return sum!(Range, F)(r, seed); } - - //F fsum(Range)(Range r, F seed) - // if ( - // isInputRange!Range && - // isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && - // !isInfinite!Range && - // ( - // summation != Summation.Pairwise || - // hasLength!Range && hasSlicing!Range - // ) - // ) } ///ditto From 5aa11cb3e892d335a3bc94c99e80532141772d16 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 22:05:09 +0300 Subject: [PATCH 24/46] fix fabs, add bug workaround --- std/numeric/summation.d | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 28db77b033c..d1893e0881f 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -12,7 +12,7 @@ module std.numeric.summation; import std.traits; import std.typecons; import std.range.primitives; -import std.math : isNaN, isFinite, isInfinity, signbit, frexp; +import std.math : isNaN, isFinite, isInfinity, signbit, frexp, fabs; /++ @@ -158,14 +158,25 @@ unittest { import std.algorithm; auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); const r = 20000; - assert(r != ar.fsum!(Summation.Naive)); - assert(r != ar.fsum!(Summation.Pairwise)); - assert(r != ar.fsum!(Summation.Kahan)); assert(r == ar.fsum!(Summation.KBN)); assert(r == ar.fsum!(Summation.KB2)); assert(r == ar.fsum); //Summation.Precise } +// FIXME +// Fails for 32bit systems. +// See also https://issues.dlang.org/show_bug.cgi?id=13474#c7 +// and https://github.com/D-Programming-Language/phobos/pull/2513 +version(none) +unittest { + import std.algorithm; + auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); + const r = 20000; + assert(r != ar.fsum!(Summation.Naive)); + assert(r != ar.fsum!(Summation.Pairwise)); + assert(r != ar.fsum!(Summation.Kahan)); +} + /++ $(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for user defined types. +/ @@ -764,6 +775,7 @@ template isComplex(C) // FIXME (perfomance issue): fabs in std.math available only for for real. F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter + if (!is(Unqual!F == real)) { if (__ctfe) { From 4cffbfe91443df2560b188d96da89ffdb47b9839 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 22:34:12 +0300 Subject: [PATCH 25/46] remove std.math.fabs usage --- std/numeric/summation.d | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index d1893e0881f..59d80dd90fa 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -12,7 +12,7 @@ module std.numeric.summation; import std.traits; import std.typecons; import std.range.primitives; -import std.math : isNaN, isFinite, isInfinity, signbit, frexp, fabs; +import std.math : isNaN, isFinite, isInfinity, signbit, frexp; /++ @@ -775,7 +775,6 @@ template isComplex(C) // FIXME (perfomance issue): fabs in std.math available only for for real. F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter - if (!is(Unqual!F == real)) { if (__ctfe) { From 50796cf73370a6fb12f785fccf678dd17b634bf3 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 9 Feb 2015 22:48:31 +0300 Subject: [PATCH 26/46] comment code --- std/numeric/summation.d | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 59d80dd90fa..8d15721fae7 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -167,15 +167,14 @@ unittest { // Fails for 32bit systems. // See also https://issues.dlang.org/show_bug.cgi?id=13474#c7 // and https://github.com/D-Programming-Language/phobos/pull/2513 -version(none) -unittest { - import std.algorithm; - auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); - const r = 20000; - assert(r != ar.fsum!(Summation.Naive)); - assert(r != ar.fsum!(Summation.Pairwise)); - assert(r != ar.fsum!(Summation.Kahan)); -} +//unittest { +// import std.algorithm; +// auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); +// const r = 20000; +// assert(r != ar.fsum!(Summation.Naive)); +// assert(r != ar.fsum!(Summation.Pairwise)); +// assert(r != ar.fsum!(Summation.Kahan)); +//} /++ $(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for user defined types. From c8b937dd6759eaa65310d659a2e91b2f583f7ae0 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 12:21:45 +0300 Subject: [PATCH 27/46] update math imports --- std/numeric/summation.d | 62 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 8d15721fae7..5c9f80654ec 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -12,7 +12,7 @@ module std.numeric.summation; import std.traits; import std.typecons; import std.range.primitives; -import std.math : isNaN, isFinite, isInfinity, signbit, frexp; +import std.math; /++ @@ -21,7 +21,6 @@ Computes accurate sum of binary logarithms of input range $(D r). ElementType!Range sumOfLog2s(Range)(Range r) if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) { - import std.math : frexp, log2; long exp = 0; Unqual!(typeof(return)) x = 1; foreach (e; r) @@ -158,6 +157,7 @@ unittest { import std.algorithm; auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); const r = 20000; + assert(r != ar.fsum!(Summation.Pairwise)); assert(r == ar.fsum!(Summation.KBN)); assert(r == ar.fsum!(Summation.KB2)); assert(r == ar.fsum); //Summation.Precise @@ -167,14 +167,14 @@ unittest { // Fails for 32bit systems. // See also https://issues.dlang.org/show_bug.cgi?id=13474#c7 // and https://github.com/D-Programming-Language/phobos/pull/2513 -//unittest { -// import std.algorithm; -// auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); -// const r = 20000; -// assert(r != ar.fsum!(Summation.Naive)); -// assert(r != ar.fsum!(Summation.Pairwise)); -// assert(r != ar.fsum!(Summation.Kahan)); -//} +version(none) +unittest { + import std.algorithm; + auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); + const r = 20000; + assert(r != ar.fsum!(Summation.Naive)); + assert(r != ar.fsum!(Summation.Kahan)); +} /++ $(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for user defined types. @@ -772,27 +772,27 @@ template isComplex(C) enum bool isComplex = is(C : Complex!F, F); } -// FIXME (perfomance issue): fabs in std.math available only for for real. -F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter -{ - if (__ctfe) - { - return f < 0 ? -f : f; - } - else - { - version(LDC) - { - import ldc.intrinsics : llvm_fabs; - return llvm_fabs(f); - } - else - { - import core.stdc.tgmath : fabs; - return fabs(f); - } - } -} +//// FIXME (perfomance issue): fabs in std.math available only for for real. +//F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter +//{ +// if (__ctfe) +// { +// return f < 0 ? -f : f; +// } +// else +// { +// version(LDC) +// { +// import ldc.intrinsics : llvm_fabs; +// return llvm_fabs(f); +// } +// else +// { +// import core.stdc.tgmath : fabs; +// return fabs(f); +// } +// } +//} template isSummable(Range, F) { From 02198aa3dc9a70d02c8f37cd56034dbfc35b0a94 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 12:41:02 +0300 Subject: [PATCH 28/46] minor fix --- std/numeric/summation.d | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 5c9f80654ec..577e458ca27 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -42,6 +42,7 @@ ElementType!Range sumOfLog2s(Range)(Range r) /// unittest { + import std.math, std.numeric; assert(sumOfLog2s(new double[0]) == 0); assert(sumOfLog2s([0.0L]) == -real.infinity); assert(sumOfLog2s([-0.0L]) == -real.infinity); @@ -105,17 +106,17 @@ Computes sum of range. template fsum(F, Summation summation = Summation.Precise) if (isFloatingPoint!F && isMutable!F) { - alias sum = Algo!summation; - F fsum(Range)(Range r) if (isSummable!(Range, F)) { + alias sum = Algo!summation; return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) if (isSummable!(Range, F)) { + alias sum = Algo!summation; return sum!(Range, F)(r, seed); } } @@ -123,17 +124,17 @@ template fsum(F, Summation summation = Summation.Precise) ///ditto template fsum(Summation summation = Summation.Precise) { - alias sum = Algo!summation; - Unqual!(ForeachType!Range) fsum(Range)(Range r) if (isSummable!(Range, Unqual!(ForeachType!Range))) { + alias sum = Algo!summation; return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) if (isSummable!(Range, F)) { + alias sum = Algo!summation; return sum!(F, Range)(r, seed); } } From 54196d56f9cba5ea14df7ec87770ed3ede6f21b9 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 13:01:12 +0300 Subject: [PATCH 29/46] workaround --- std/numeric/summation.d | 146 ++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 577e458ca27..dcaca19d328 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -107,14 +107,14 @@ template fsum(F, Summation summation = Summation.Precise) if (isFloatingPoint!F && isMutable!F) { F fsum(Range)(Range r) - if (isSummable!(Range, F)) + //if (isSummable!(Range, F)) { alias sum = Algo!summation; return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) - if (isSummable!(Range, F)) + //if (isSummable!(Range, F)) { alias sum = Algo!summation; return sum!(Range, F)(r, seed); @@ -125,14 +125,14 @@ template fsum(F, Summation summation = Summation.Precise) template fsum(Summation summation = Summation.Precise) { Unqual!(ForeachType!Range) fsum(Range)(Range r) - if (isSummable!(Range, Unqual!(ForeachType!Range))) + //if (isSummable!(Range, Unqual!(ForeachType!Range))) { alias sum = Algo!summation; return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) - if (isSummable!(Range, F)) + //if (isSummable!(Range, F)) { alias sum = Algo!summation; return sum!(F, Range)(r, seed); @@ -232,12 +232,12 @@ unittest import std.complex; Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; Complex!double r = complex(10, 14); - assert(r == ar.fsum!(Summation.Fast)); - assert(r == ar.fsum!(Summation.Naive)); - assert(r == ar.fsum!(Summation.Pairwise)); - assert(r == ar.fsum!(Summation.Kahan)); - assert(r == ar.fsum!(Summation.KBN)); - assert(r == ar.fsum!(Summation.KB2)); + //assert(r == ar.fsum!(Summation.Fast)); + //assert(r == ar.fsum!(Summation.Naive)); + //assert(r == ar.fsum!(Summation.Pairwise)); + //assert(r == ar.fsum!(Summation.Kahan)); + //assert(r == ar.fsum!(Summation.KBN)); + //assert(r == ar.fsum!(Summation.KB2)); assert(r == ar.fsum); //Summation.Precise } @@ -441,46 +441,46 @@ public: } } - /++ - Adds $(D x) to the internal partial sums. - This operation doesn't re-establish special - value semantics across iterations (i.e. handling -inf + inf). - Preconditions: $(D isFinite(x)). - +/ - package void unsafePut(F x) - in { - assert(.isFinite(x)); - } - body { - size_t i; - foreach (y; partials[]) - { - F h = x + y; - debug(numeric) assert(.isFinite(h)); - F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); - debug(numeric) assert(.isFinite(l)); - if (l) - { - partials[i++] = l; - } - x = h; - } - partials.length = i; - if (x) - { - partials.put(x); - } - } - - /// - unittest { - import std.math, std.algorithm, std.range; - auto r = iota(1, 1001).map!(a => (-1.0).pow(a)/a); - Summator!double s = 0.0; - foreach(e; r) - s.unsafePut(e); - assert(s.sum() == -0.69264743055982025); - } + ///++ + //Adds $(D x) to the internal partial sums. + //This operation doesn't re-establish special + //value semantics across iterations (i.e. handling -inf + inf). + //Preconditions: $(D isFinite(x)). + //+/ + //package void unsafePut(F x) + //in { + // assert(.isFinite(x)); + //} + //body { + // size_t i; + // foreach (y; partials[]) + // { + // F h = x + y; + // debug(numeric) assert(.isFinite(h)); + // F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); + // debug(numeric) assert(.isFinite(l)); + // if (l) + // { + // partials[i++] = l; + // } + // x = h; + // } + // partials.length = i; + // if (x) + // { + // partials.put(x); + // } + //} + + ///// + //unittest { + // import std.math, std.algorithm, std.range; + // auto r = iota(1, 1001).map!(a => (-1.0).pow(a)/a); + // Summator!double s = 0.0; + // foreach(e; r) + // s.unsafePut(e); + // assert(s.sum() == -0.69264743055982025); + //} /++ Returns the value of the sum, rounded to the nearest representable @@ -767,11 +767,11 @@ unittest private: -template isComplex(C) -{ - import std.complex : Complex; - enum bool isComplex = is(C : Complex!F, F); -} +//template isComplex(C) +//{ +// import std.complex : Complex; +// enum bool isComplex = is(C : Complex!F, F); +//} //// FIXME (perfomance issue): fabs in std.math available only for for real. //F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter @@ -795,21 +795,21 @@ template isComplex(C) // } //} -template isSummable(Range, F) -{ - enum bool isSummable = - isInputRange!Range && - isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && - !isInfinite!Range && - __traits(compiles, - { - F a = 0.1, b, c; - c = a + b; - c = a - b; - a += b; - a -= b; - }); -} +//template isSummable(Range, F) +//{ +// enum bool isSummable = +// isInputRange!Range && +// isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && +// !isInfinite!Range && +// __traits(compiles, +// { +// F a = 0.1, b, c; +// c = a + b; +// c = a - b; +// a += b; +// a -= b; +// }); +//} /++ Naive summation algorithm. @@ -902,7 +902,7 @@ s := s + c --------------------- +/ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) - if (isFloatingPoint!F || isComplex!F) + //if (isFloatingPoint!F || isComplex!F) { F c = 0.0; static if (isFloatingPoint!F) @@ -970,7 +970,7 @@ RETURN s+cs+ccs --------------------- +/ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) - if (isFloatingPoint!F || isComplex!F) + //if (isFloatingPoint!F || isComplex!F) { F cs = 0.0; F ccs = 0.0; @@ -1066,7 +1066,7 @@ unittest Precise summation. +/ F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0.0) - if (isFloatingPoint!F || isComplex!F) + //if (isFloatingPoint!F || isComplex!F) { static if (isFloatingPoint!F) { From 728ce84f5eacfcfca73844ff00ed35a9e40597de Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 14:29:38 +0300 Subject: [PATCH 30/46] ICE bug workaround --- std/numeric/summation.d | 149 ++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 73 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index dcaca19d328..2fb9c5760c6 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,7 +7,7 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.numeric.summation; +module std.summation; import std.traits; import std.typecons; @@ -107,14 +107,14 @@ template fsum(F, Summation summation = Summation.Precise) if (isFloatingPoint!F && isMutable!F) { F fsum(Range)(Range r) - //if (isSummable!(Range, F)) + if (isSummable!(Range, F)) { alias sum = Algo!summation; - return sum!(Range, typeof(return))(r); + return sum!(Range, F)(r); } - F fsum(F, Range)(F seed, Range r) - //if (isSummable!(Range, F)) + F fsum(Range)(F seed, Range r) + if (isSummable!(Range, F)) { alias sum = Algo!summation; return sum!(Range, F)(r, seed); @@ -125,17 +125,17 @@ template fsum(F, Summation summation = Summation.Precise) template fsum(Summation summation = Summation.Precise) { Unqual!(ForeachType!Range) fsum(Range)(Range r) - //if (isSummable!(Range, Unqual!(ForeachType!Range))) + if (isSummable!(Range, Unqual!(ForeachType!Range))) { alias sum = Algo!summation; return sum!(Range, typeof(return))(r); } F fsum(F, Range)(F seed, Range r) - //if (isSummable!(Range, F)) + if (isSummable!(Range, F)) { alias sum = Algo!summation; - return sum!(F, Range)(r, seed); + return sum!(Range, F)(r, seed); } } @@ -146,11 +146,11 @@ unittest auto ar = 1000 .iota .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) - .chain([-(1.7.pow(1000))]); + ; //Summation.Precise is default - assert(ar.fsum == -1.0); - assert(ar.retro.fsum!real == -1.0); + assert(fsum(ar.chain([-(1.7.pow(1000))])) == -1.0); + assert(fsum!real(-(1.7.pow(1000)), ar.array.retro) == -1.0); } /// @@ -158,7 +158,6 @@ unittest { import std.algorithm; auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); const r = 20000; - assert(r != ar.fsum!(Summation.Pairwise)); assert(r == ar.fsum!(Summation.KBN)); assert(r == ar.fsum!(Summation.KB2)); assert(r == ar.fsum); //Summation.Precise @@ -168,20 +167,20 @@ unittest { // Fails for 32bit systems. // See also https://issues.dlang.org/show_bug.cgi?id=13474#c7 // and https://github.com/D-Programming-Language/phobos/pull/2513 -version(none) +//version(none) unittest { import std.algorithm; auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); const r = 20000; assert(r != ar.fsum!(Summation.Naive)); assert(r != ar.fsum!(Summation.Kahan)); + assert(r != ar.fsum!(Summation.Pairwise)); } /++ $(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for user defined types. +/ -unittest -{ +unittest { static struct Quaternion(F) if (isFloatingPoint!F) { @@ -232,12 +231,12 @@ unittest import std.complex; Complex!double[] ar = [complex(1.0, 2), complex(2, 3), complex(3, 4), complex(4, 5)]; Complex!double r = complex(10, 14); - //assert(r == ar.fsum!(Summation.Fast)); - //assert(r == ar.fsum!(Summation.Naive)); - //assert(r == ar.fsum!(Summation.Pairwise)); - //assert(r == ar.fsum!(Summation.Kahan)); - //assert(r == ar.fsum!(Summation.KBN)); - //assert(r == ar.fsum!(Summation.KB2)); + assert(r == ar.fsum!(Summation.Fast)); + assert(r == ar.fsum!(Summation.Naive)); + assert(r == ar.fsum!(Summation.Pairwise)); + assert(r == ar.fsum!(Summation.Kahan)); + assert(r == ar.fsum!(Summation.KBN)); + assert(r == ar.fsum!(Summation.KB2)); assert(r == ar.fsum); //Summation.Precise } @@ -421,7 +420,11 @@ public: h = x + y; } debug(numeric) assert(h.isFinite); - F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); + F l; + if(fabs(x) < fabs(y)) + l = x - (h - y); + else + l = y - (h - x); debug(numeric) assert(l.isFinite); if (l) { @@ -767,54 +770,54 @@ unittest private: -//template isComplex(C) -//{ -// import std.complex : Complex; -// enum bool isComplex = is(C : Complex!F, F); -//} - -//// FIXME (perfomance issue): fabs in std.math available only for for real. -//F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter -//{ -// if (__ctfe) -// { -// return f < 0 ? -f : f; -// } -// else -// { -// version(LDC) -// { -// import ldc.intrinsics : llvm_fabs; -// return llvm_fabs(f); -// } -// else -// { -// import core.stdc.tgmath : fabs; -// return fabs(f); -// } -// } -//} - -//template isSummable(Range, F) -//{ -// enum bool isSummable = -// isInputRange!Range && -// isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && -// !isInfinite!Range && -// __traits(compiles, -// { -// F a = 0.1, b, c; -// c = a + b; -// c = a - b; -// a += b; -// a -= b; -// }); -//} +template isComplex(C) +{ + import std.complex : Complex; + enum bool isComplex = is(C : Complex!F, F); +} + +// FIXME (perfomance issue): fabs in std.math available only for for real. +F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter +{ + if (__ctfe) + { + return f < 0 ? -f : f; + } + else + { + version(LDC) + { + import ldc.intrinsics : llvm_fabs; + return llvm_fabs(f); + } + else + { + import core.stdc.tgmath : fabs; + return fabs(f); + } + } +} + +template isSummable(Range, F) +{ + enum bool isSummable = + isInputRange!Range && + isImplicitlyConvertible!(Unqual!(ForeachType!Range), F) && + !isInfinite!Range && + __traits(compiles, + { + F a = 0.1, b, c; + c = a + b; + c = a - b; + a += b; + a -= b; + }); +} /++ Naive summation algorithm. +/ -F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) +F sumNaive(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) { foreach (x; r) { @@ -864,7 +867,7 @@ s := t END DO --------------------- +/ -F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) +F sumKahan(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) { F c = 0.0; F y; // do not declare in the loop (algo can be used for matrixes and etc) @@ -901,8 +904,8 @@ END DO s := s + c --------------------- +/ -F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) - //if (isFloatingPoint!F || isComplex!F) +F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) + if (isFloatingPoint!F || isComplex!F) { F c = 0.0; static if (isFloatingPoint!F) @@ -969,8 +972,8 @@ END FOR RETURN s+cs+ccs --------------------- +/ -F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0.0) - //if (isFloatingPoint!F || isComplex!F) +F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) + if (isFloatingPoint!F || isComplex!F) { F cs = 0.0; F ccs = 0.0; @@ -1065,8 +1068,8 @@ unittest /++ Precise summation. +/ -F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0.0) - //if (isFloatingPoint!F || isComplex!F) +F sumPrecise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed = 0) + if (isFloatingPoint!F || isComplex!F) { static if (isFloatingPoint!F) { From c5043c29119db7e2dd47484b71d29fd51a47faa1 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 14:30:03 +0300 Subject: [PATCH 31/46] fix module name --- std/numeric/summation.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 2fb9c5760c6..1ffe54e76c2 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,7 +7,7 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.summation; +module std.numeric.summation; import std.traits; import std.typecons; From 8c8c8c187831706ed095ecabccd5be0cced1d6a3 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 15:34:19 +0300 Subject: [PATCH 32/46] dmd bug workaround --- std/numeric/summation.d | 204 +++++++++++++++++++++++++--------------- 1 file changed, 126 insertions(+), 78 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 1ffe54e76c2..c3c36ab213f 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,7 +7,7 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.numeric.summation; +module std.summation; import std.traits; import std.typecons; @@ -150,7 +150,7 @@ unittest //Summation.Precise is default assert(fsum(ar.chain([-(1.7.pow(1000))])) == -1.0); - assert(fsum!real(-(1.7.pow(1000)), ar.array.retro) == -1.0); + assert(fsum!real(-(1.7.pow(1000)), ar.retro) == -1.0); } /// @@ -311,7 +311,8 @@ private: { F x = s; s = x + y; - F l = y - (s - x); + F d = s - x; + F l = y - d; debug(numeric) { assert(x.isFinite); @@ -348,9 +349,11 @@ private: F x = o * M; F y = partials[$-1] / 2; F h = x + y; - F l = (y - (h - x)) * 2; + F d = h - x; + F l = (y - d) * 2; y = h * 2; - if (!.isInfinity(y) || partials.length > 1 && !signbit(l * partials[$-2]) && (h + l) - h == l) + d = h + l; + if (!.isInfinity(y) || partials.length > 1 && !signbit(l * partials[$-2]) && d - h == l) return 0; } return F.infinity * o; @@ -422,9 +425,15 @@ public: debug(numeric) assert(h.isFinite); F l; if(fabs(x) < fabs(y)) - l = x - (h - y); + { + F t = h - y; + l = x - t; + } else - l = y - (h - x); + { + F t = h - x; + l = y - t; + } debug(numeric) assert(l.isFinite); if (l) { @@ -444,46 +453,56 @@ public: } } - ///++ - //Adds $(D x) to the internal partial sums. - //This operation doesn't re-establish special - //value semantics across iterations (i.e. handling -inf + inf). - //Preconditions: $(D isFinite(x)). - //+/ - //package void unsafePut(F x) - //in { - // assert(.isFinite(x)); - //} - //body { - // size_t i; - // foreach (y; partials[]) - // { - // F h = x + y; - // debug(numeric) assert(.isFinite(h)); - // F l = fabs(x) < fabs(y) ? x - (h - y) : y - (h - x); - // debug(numeric) assert(.isFinite(l)); - // if (l) - // { - // partials[i++] = l; - // } - // x = h; - // } - // partials.length = i; - // if (x) - // { - // partials.put(x); - // } - //} - - ///// - //unittest { - // import std.math, std.algorithm, std.range; - // auto r = iota(1, 1001).map!(a => (-1.0).pow(a)/a); - // Summator!double s = 0.0; - // foreach(e; r) - // s.unsafePut(e); - // assert(s.sum() == -0.69264743055982025); - //} + /++ + Adds $(D x) to the internal partial sums. + This operation doesn't re-establish special + value semantics across iterations (i.e. handling -inf + inf). + Preconditions: $(D isFinite(x)). + +/ + package void unsafePut(F x) + in { + assert(.isFinite(x)); + } + body { + size_t i; + foreach (y; partials[]) + { + F h = x + y; + debug(numeric) assert(.isFinite(h)); + F l; + if(fabs(x) < fabs(y)) + { + F t = h - y; + l = x - t; + } + else + { + F t = h - x; + l = y - t; + } + debug(numeric) assert(.isFinite(l)); + if (l) + { + partials[i++] = l; + } + x = h; + } + partials.length = i; + if (x) + { + partials.put(x); + } + } + + /// + unittest { + import std.math, std.algorithm, std.range; + auto r = iota(1, 1001).map!(a => (-1.0).pow(a)/a); + Summator!double s = 0.0; + foreach(e; r) + s.unsafePut(e); + assert(s.sum() == -0.69264743055982025); + } /++ Returns the value of the sum, rounded to the nearest representable @@ -522,7 +541,8 @@ public: y /= 2; F x = of * M; immutable F h = x + y; - F l = (y - (h - x)) * 2; + F t = h - x; + F l = (y - t) * 2; y = h * 2; if (y.isInfinity) { @@ -776,27 +796,27 @@ template isComplex(C) enum bool isComplex = is(C : Complex!F, F); } -// FIXME (perfomance issue): fabs in std.math available only for for real. -F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter -{ - if (__ctfe) - { - return f < 0 ? -f : f; - } - else - { - version(LDC) - { - import ldc.intrinsics : llvm_fabs; - return llvm_fabs(f); - } - else - { - import core.stdc.tgmath : fabs; - return fabs(f); - } - } -} +//// FIXME (perfomance issue): fabs in std.math available only for for real. +//F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter +//{ +// if (__ctfe) +// { +// return f < 0 ? -f : f; +// } +// else +// { +// version(LDC) +// { +// import ldc.intrinsics : llvm_fabs; +// return llvm_fabs(f); +// } +// else +// { +// import core.stdc.tgmath : fabs; +// return fabs(f); +// } +// } +//} template isSummable(Range, F) { @@ -914,9 +934,17 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) { F t = s + x; if (fabs(s) >= fabs(x)) - c += (s-t)+x; + { + F d = s - t; + d += x; + c += d; + } else - c += (x-t)+s; + { + F d = x - t; + d += s; + c += d; + } s = t; } } @@ -937,7 +965,9 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) s.im = x.im; x.im = t_im; } - c += (s-t)+x; + F d = s - t; + d += x; + c += d; s = t; } } @@ -984,15 +1014,29 @@ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) F t = s + x; F c = void; if (fabs(s) >= fabs(x)) - c = (s-t)+x; + { + F d = s - t; + c = d + x; + } else - c = (x-t)+s; + { + F d = x - t; + c = d + s; + } s = t; t = cs + c; if (fabs(cs) >= fabs(c)) - ccs += (cs-t)+c; + { + F d = cs - t; + d += c; + ccs += d; + } else - ccs += (c-t)+cs; + { + F d = c - t; + d += cs; + ccs += d; + } cs = t; } } @@ -1027,11 +1071,15 @@ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) cs.im = c.im; c.im = t_im; } - ccs += (cs-t)+c; + F d = cs - t; + d += c; + ccs += d; cs = t; } } - return s+cs+ccs; // no rounding in between + cs += ccs; + s += cs; + return s; } unittest From 6fd02c90aec1e1979d78be39bf98d2f0291d5807 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 15:43:35 +0300 Subject: [PATCH 33/46] fix module name --- std/numeric/summation.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index c3c36ab213f..b2c355b62e0 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,7 +7,7 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.summation; +module std.numeric.summation; import std.traits; import std.typecons; From 8ff88a473cbd8f8d8e96f9ab9be66997e09e8b96 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 16:11:30 +0300 Subject: [PATCH 34/46] dmd bug workaround --- std/numeric/summation.d | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index b2c355b62e0..21234dbdd56 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,7 +7,7 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.numeric.summation; +module std.summation; import std.traits; import std.typecons; @@ -330,7 +330,8 @@ private: { l *= 2; x = s + l; - if (l == x - s) + F t = x - s; + if (l == t) s = x; } _break = true; @@ -353,7 +354,8 @@ private: F l = (y - d) * 2; y = h * 2; d = h + l; - if (!.isInfinity(y) || partials.length > 1 && !signbit(l * partials[$-2]) && d - h == l) + F t = d - h; + if (!.isInfinity(y) || partials.length > 1 && !signbit(l * partials[$-2]) && t == l) return 0; } return F.infinity * o; @@ -548,7 +550,8 @@ public: { // overflow, except in edge case... x = h + l; - y = parts.length && x - h == l && !signbit(l*parts[$-1]) ? + t = x - h; + y = parts.length && t == l && !signbit(l*parts[$-1]) ? x * 2 : F.infinity * of; parts = null; From 4d97c13fe86c26888ce069e561039b86f484b0bb Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 16:13:57 +0300 Subject: [PATCH 35/46] fix module name --- std/numeric/summation.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 21234dbdd56..4261733d226 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,7 +7,7 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.summation; +module std.numeric.summation; import std.traits; import std.typecons; From e5c05ff0159fe7d98070e8b52970498eea4a1f87 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 16:33:22 +0300 Subject: [PATCH 36/46] std.math workaround --- std/math.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/std/math.d b/std/math.d index f37ae3abd81..64e318b790c 100644 --- a/std/math.d +++ b/std/math.d @@ -5467,7 +5467,8 @@ Unqual!F pow(F, G)(F x, G n) @nogc @trusted pure nothrow case -1: return 1 / x; case -2: - return 1 / (x * x); + F y = x * x; + return 1 / y; default: } From 3976f2b7ea6e72d6cae078516090a0539aeb1bdb Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 14 Feb 2015 17:31:15 +0300 Subject: [PATCH 37/46] move unittest --- std/numeric/summation.d | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 4261733d226..89045fa8f00 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -139,20 +139,6 @@ template fsum(Summation summation = Summation.Precise) } } -/// -unittest -{ - import std.math, std.algorithm, std.range; - auto ar = 1000 - .iota - .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) - ; - - //Summation.Precise is default - assert(fsum(ar.chain([-(1.7.pow(1000))])) == -1.0); - assert(fsum!real(-(1.7.pow(1000)), ar.retro) == -1.0); -} - /// unittest { import std.algorithm; @@ -177,6 +163,20 @@ unittest { assert(r != ar.fsum!(Summation.Pairwise)); } +/// +unittest +{ + import std.math, std.algorithm, std.range; + auto ar = 1000 + .iota + .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) + ; + + //Summation.Precise is default + assert(fsum(ar.chain([-(1.7.pow(1000))])) == -1.0); + assert(fsum!real(-(1.7.pow(1000)), ar.retro) == -1.0); +} + /++ $(D Naive), $(D Pairwise) and $(D Kahan) algorithms can be used for user defined types. +/ From 31772b6a3b99ec52b531a644cb55b022c1962721 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 08:54:13 +0300 Subject: [PATCH 38/46] fix return behaviour --- std/numeric/summation.d | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 89045fa8f00..de2bf99cff8 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -871,7 +871,9 @@ F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r) F sumPairwise(Range, F = Unqual!(ForeachType!Range))(Range r, F seed) { - return sumPairwise!Range(r) + seed; + F s = seed; + s += sumPairwise!Range(r); + return s; } @@ -974,7 +976,8 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) s = t; } } - return s + c; + s += c; + return s; } From b9c47c7ea4aa075128fb5be9c14ed700b09b3962 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 13:22:11 +0300 Subject: [PATCH 39/46] workaround for Issue 13474 --- std/numeric/summation.d | 197 +++++++++++++++++++++++++--------------- 1 file changed, 126 insertions(+), 71 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index de2bf99cff8..ac2e441601a 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,13 +7,31 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.numeric.summation; +module std.summation; import std.traits; import std.typecons; import std.range.primitives; import std.math; +private template SummationType(F) + if (isFloatingPoint!F || isComplex!F) +{ + version(X86) //workaround for Issue 13474 + { + static if(!is(Unqual!F == real) && (isComplex!F || !is(Unqual!(typeof(F.init.re)) == real))) + pragma(msg, "Warning: Summation algorithms on x86 use 80bit representation for single and double floating point numbers."); + static if(isComplex!F) + { + import std.complex : Complex; + alias SummationType = Complex!real; + } + else + alias SummationType = real; + } + else + alias SummationType = F; +} /++ Computes accurate sum of binary logarithms of input range $(D r). @@ -153,14 +171,20 @@ unittest { // Fails for 32bit systems. // See also https://issues.dlang.org/show_bug.cgi?id=13474#c7 // and https://github.com/D-Programming-Language/phobos/pull/2513 -//version(none) -unittest { - import std.algorithm; - auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); - const r = 20000; - assert(r != ar.fsum!(Summation.Naive)); - assert(r != ar.fsum!(Summation.Kahan)); - assert(r != ar.fsum!(Summation.Pairwise)); +version(X86) +{ + +} +else +{ + unittest { + import std.algorithm; + auto ar = [1, 1e100, 1, -1e100].map!(a => a*10000); + const r = 20000; + assert(r != ar.fsum!(Summation.Naive)); + assert(r != ar.fsum!(Summation.Kahan)); + assert(r != ar.fsum!(Summation.Pairwise)); + } } /// @@ -258,13 +282,14 @@ Dickinson's post at . See those links for more details, proofs and other references. IEEE 754R floating point semantics are assumed. +/ -struct Summator(F) - if (isFloatingPoint!F && isMutable!F) +struct Summator(T) + if (isFloatingPoint!T && isMutable!T) { import std.internal.scopebuffer; private: - enum F M = (cast(F)(2)) ^^ (F.max_exp - 1); + alias F = SummationType!T; + enum F M = (cast(F)(2)) ^^ (T.max_exp - 1); F[32] scopeBufferArray = void; ScopeBuffer!F partials; //sum for NaN and infinity. @@ -355,8 +380,16 @@ private: y = h * 2; d = h + l; F t = d - h; - if (!.isInfinity(y) || partials.length > 1 && !signbit(l * partials[$-2]) && t == l) - return 0; + version(X86) + { + if (!.isInfinity(cast(T)y) || !.isInfinity(sum())) + return 0; + } + else + { + if (!.isInfinity(cast(T)y) || partials.length > 1 && !signbit(l * partials[$-2]) && t == l) + return 0; + } } return F.infinity * o; } @@ -393,15 +426,16 @@ public: } ///Adds $(D x) to the internal partial sums. - void put(F x) + void put(T _x) { + F x = _x; if (.isFinite(x)) { size_t i; foreach (y; partials[]) { F h = x + y; - if (.isInfinity(h)) + if (.isInfinity(cast(T)h)) { if (fabs(x) < fabs(y)) { @@ -499,18 +533,18 @@ public: /// unittest { import std.math, std.algorithm, std.range; - auto r = iota(1, 1001).map!(a => (-1.0).pow(a)/a); - Summator!double s = 0.0; - foreach(e; r) - s.unsafePut(e); - assert(s.sum() == -0.69264743055982025); + auto r = iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); + Summator!double s = 0; + put(s, r); + s -= 1.7.pow(1000); + assert(s.sum() == -1); } /++ Returns the value of the sum, rounded to the nearest representable floating-point number using the round-half-to-even rule. +/ - F sum() const + T sum() const { debug(numeric) { @@ -546,7 +580,7 @@ public: F t = h - x; F l = (y - t) * 2; y = h * 2; - if (y.isInfinity) + if (.isInfinity(cast(T)y)) { // overflow, except in edge case... x = h + l; @@ -589,14 +623,14 @@ public: } ///Returns $(D Summator) with extended internal partial sums. - T opCast(T : Summator!P, P)() + C opCast(C : Summator!P, P)() if ( - isMutable!T && - P.max_exp >= F.max_exp && - P.mant_dig >= F.mant_dig + isMutable!C && + P.max_exp >= T.max_exp && + P.mant_dig >= T.mant_dig ) { - static if (is(P == F)) + static if (is(P == T)) return this; else { @@ -608,7 +642,7 @@ public: { ret.partials.put(p); } - enum exp_diff = P.max_exp / F.max_exp; + enum exp_diff = P.max_exp / T.max_exp; static if (exp_diff) { if (ret.o) @@ -616,7 +650,7 @@ public: immutable f = ret.o / exp_diff; immutable t = cast(int)(ret.o % exp_diff); ret.o = f; - ret.put((P(2) ^^ F.max_exp) * t); + ret.put((P(2) ^^ T.max_exp) * t); } } return ret; @@ -628,23 +662,21 @@ public: { import std.math; float M = 2.0f ^^ (float.max_exp-1); + double N = 2.0 ^^ (float.max_exp-1); auto s = Summator!float(0); //float summator s += M; s += M; - assert(M+M == s.sum()); - assert(M+M == float.infinity); - - double N = 2.0 ^^ (float.max_exp-1); + assert(float.infinity == s.sum()); auto e = cast(Summator!double) s; + assert(isFinite(e.sum())); assert(N+N == e.sum()); - assert(isFinite(N+N)); } /++ $(D cast(F)) operator overloading. Returns $(D cast(T)sum()). See also: $(D cast) +/ - T opCast(T)() if (is(Unqual!T == F)) + F opCast(C)() if (is(Unqual!C == T)) { return sum(); } @@ -691,13 +723,14 @@ public: /// unittest { import std.math, std.algorithm, std.range; - auto r1 = iota( 1, 501 ).map!(a => (-1.0).pow(a)/a); - auto r2 = iota(501, 1001).map!(a => (-1.0).pow(a)/a); + auto r1 = iota(500).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); + auto r2 = iota(500, 1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); Summator!double s1 = 0.0, s2 = 0.0; foreach (e; r1) s1 += e; foreach (e; r2) s2 -= e; s1 -= s2; - assert(s1.sum() == -0.69264743055982025); + s1 -= 1.7.pow(1000); + assert(s1.sum() == -1); } ///Returns $(D true) if current sum is a NaN. @@ -777,15 +810,25 @@ unittest tuple([M, M, -1e307], 1.6976931348623159e+308), tuple([1e16, 1., 1e-16], 10000000000000002.0), ]; - foreach (test; tests) + foreach (i, test; tests) { foreach (t; test[0]) summator.put(t); auto r = test[1]; - assert(summator.isNaN() == r.isNaN()); - assert(summator.isFinite() == r.isFinite()); - assert(summator.isInfinity() == r.isInfinity()); auto s = summator.sum; - assert(s == r || s.isNaN && r.isNaN); + version(X86) + { + assert(summator.isNaN() == r.isNaN()); + assert(summator.isFinite() == r.isFinite() || r == -double.max && s == -double.infinity || r == double.max && s == double.infinity); + assert(summator.isInfinity() == r.isInfinity() || r == -double.max && s == -double.infinity || r == double.max && s == double.infinity); + assert(s == r || nextDown(s) <= r && nextUp(s) >= r || s.isNaN && r.isNaN); + } + else + { + assert(summator.isNaN() == r.isNaN()); + assert(summator.isFinite() == r.isFinite()); + assert(summator.isInfinity() == r.isInfinity()); + assert(s == r || s.isNaN && r.isNaN); + } summator = 0; } } @@ -799,27 +842,35 @@ template isComplex(C) enum bool isComplex = is(C : Complex!F, F); } -//// FIXME (perfomance issue): fabs in std.math available only for for real. -//F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter -//{ -// if (__ctfe) -// { -// return f < 0 ? -f : f; -// } -// else -// { -// version(LDC) -// { -// import ldc.intrinsics : llvm_fabs; -// return llvm_fabs(f); -// } -// else -// { -// import core.stdc.tgmath : fabs; -// return fabs(f); -// } -// } -//} +version(X86) +{ + +} +else +{ + // FIXME (perfomance issue): fabs in std.math available only for for real. + F fabs(F)(F f) //+-0, +-NaN, +-inf doesn't matter + { + if (__ctfe) + { + return f < 0 ? -f : f; + } + else + { + version(LDC) + { + import ldc.intrinsics : llvm_fabs; + return llvm_fabs(f); + } + else + { + import core.stdc.tgmath : fabs; + return fabs(f); + } + } + } +} + template isSummable(Range, F) { @@ -929,9 +980,11 @@ END DO s := s + c --------------------- +/ -F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) - if (isFloatingPoint!F || isComplex!F) +T sumKBN(Range, T = Unqual!(ForeachType!Range))(Range r, T _s = 0) + if (isFloatingPoint!T || isComplex!T) { + alias F = SummationType!T; + F s = _s; F c = 0.0; static if (isFloatingPoint!F) { @@ -977,7 +1030,7 @@ F sumKBN(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) } } s += c; - return s; + return cast(T)s; } @@ -1008,9 +1061,11 @@ END FOR RETURN s+cs+ccs --------------------- +/ -F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) - if (isFloatingPoint!F || isComplex!F) +T sumKB2(Range, T = Unqual!(ForeachType!Range))(Range r, T _s = 0) + if (isFloatingPoint!T || isComplex!T) { + alias F = SummationType!T; + F s = _s; F cs = 0.0; F ccs = 0.0; static if (isFloatingPoint!F) @@ -1085,7 +1140,7 @@ F sumKB2(Range, F = Unqual!(ForeachType!Range))(Range r, F s = 0) } cs += ccs; s += cs; - return s; + return cast(T)s; } unittest From 4eb2150300e8ecb847719344600831e916ef5896 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 13:31:31 +0300 Subject: [PATCH 40/46] add comment --- std/numeric/summation.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index ac2e441601a..c340e2206f3 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -113,6 +113,7 @@ enum Summation Precise summation algorithm. The value of the sum is rounded to the nearest representable floating-point number using the $(LUCKY round-half-to-even rule). + Result can be differ from the exact value on $(D X86), $(D nextDown(proir) <= result && result <= nextUp(proir)). +/ Precise, } @@ -543,6 +544,7 @@ public: /++ Returns the value of the sum, rounded to the nearest representable floating-point number using the round-half-to-even rule. + Result can be differ from the exact value on $(D X86), $(D nextDown(proir) <= result && result <= nextUp(proir)). +/ T sum() const { From 22189197d82b6fd7aee897aa33250e496d1b15b2 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 13:32:16 +0300 Subject: [PATCH 41/46] fix header name --- std/numeric/summation.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index c340e2206f3..c55c851ade1 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -7,7 +7,7 @@ Authors: $(WEB 9il.github.io, Ilya Yaroshenko) Source: $(PHOBOSSRC std/numeric/_summation.d) */ -module std.summation; +module std.numeric.summation; import std.traits; import std.typecons; From 076483764ae1685c2daec11257ecf540ac4a30bc Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 13:34:18 +0300 Subject: [PATCH 42/46] remove changes in std.math --- std/math.d | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/std/math.d b/std/math.d index 64e318b790c..f37ae3abd81 100644 --- a/std/math.d +++ b/std/math.d @@ -5467,8 +5467,7 @@ Unqual!F pow(F, G)(F x, G n) @nogc @trusted pure nothrow case -1: return 1 / x; case -2: - F y = x * x; - return 1 / y; + return 1 / (x * x); default: } From 3c111b7796c4670c43e3aa88135638002c3220ef Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 13:53:19 +0300 Subject: [PATCH 43/46] fix unittest --- std/numeric/summation.d | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index c55c851ade1..53db7284bac 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -198,8 +198,9 @@ unittest ; //Summation.Precise is default - assert(fsum(ar.chain([-(1.7.pow(1000))])) == -1.0); - assert(fsum!real(-(1.7.pow(1000)), ar.retro) == -1.0); + double d = 1.7.pow(1000); + assert(fsum(ar.chain([-d])) == -1.0); + assert(fsum!real(-d, ar.retro) == -1.0); } /++ From 9c38dec187e9fc5fb03adcf306cb1e3d41d77bbe Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 14:14:59 +0300 Subject: [PATCH 44/46] update unittest --- std/numeric/summation.d | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 53db7284bac..f8a00dbc90b 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -199,8 +199,18 @@ unittest //Summation.Precise is default double d = 1.7.pow(1000); - assert(fsum(ar.chain([-d])) == -1.0); - assert(fsum!real(-d, ar.retro) == -1.0); + double s1 = fsum(ar.chain([-d])); + double s2 = fsum!real(-d, ar.retro); + version(X86) + { + assert(nextDown(-1.0) <= s1 && s1 <= nextUp(-1.0)); + assert(nextDown(-1.0) <= s2 && s2 <= nextUp(-1.0)); + } + else + { + assert(s1 == -1.0); + assert(s2 == -1.0); + } } /++ From 27e377c2b858883668a30ae7785e89ebb568fafe Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 14:32:38 +0300 Subject: [PATCH 45/46] update unittest --- std/numeric/summation.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index f8a00dbc90b..3a0b475d9b7 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -195,6 +195,7 @@ unittest auto ar = 1000 .iota .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) + .array ; //Summation.Precise is default @@ -203,8 +204,8 @@ unittest double s2 = fsum!real(-d, ar.retro); version(X86) { - assert(nextDown(-1.0) <= s1 && s1 <= nextUp(-1.0)); assert(nextDown(-1.0) <= s2 && s2 <= nextUp(-1.0)); + assert(nextDown(-1.0) <= s1 && s1 <= nextUp(-1.0)); } else { From 8c8386c2ca2b3833016d579e07b5a769e950040e Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Mon, 16 Feb 2015 15:11:18 +0300 Subject: [PATCH 46/46] pow bug workaround --- std/numeric/summation.d | 45 ++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/std/numeric/summation.d b/std/numeric/summation.d index 3a0b475d9b7..add9b33a503 100644 --- a/std/numeric/summation.d +++ b/std/numeric/summation.d @@ -191,27 +191,16 @@ else /// unittest { - import std.math, std.algorithm, std.range; - auto ar = 1000 + import core.stdc.tgmath, std.algorithm, std.range; + auto ar = 1000.0 .iota - .map!(n => 1.7.pow(n+1) - 1.7.pow(n)) + .map!(n => 1.7.pow(n+1.0) - 1.7.pow(n)) .array ; - //Summation.Precise is default - double d = 1.7.pow(1000); - double s1 = fsum(ar.chain([-d])); - double s2 = fsum!real(-d, ar.retro); - version(X86) - { - assert(nextDown(-1.0) <= s2 && s2 <= nextUp(-1.0)); - assert(nextDown(-1.0) <= s1 && s1 <= nextUp(-1.0)); - } - else - { - assert(s1 == -1.0); - assert(s2 == -1.0); - } + double d = 1.7.pow(1000.0); + assert(fsum(ar.chain([-d])) == -1.0); + assert(fsum!real(-d, ar.retro) == -1.0); } /++ @@ -545,11 +534,11 @@ public: /// unittest { - import std.math, std.algorithm, std.range; - auto r = iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); + import core.stdc.tgmath, std.algorithm, std.range; + auto r = iota(1000.0).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); Summator!double s = 0; put(s, r); - s -= 1.7.pow(1000); + s -= 1.7.pow(1000.0); assert(s.sum() == -1); } @@ -736,14 +725,14 @@ public: /// unittest { - import std.math, std.algorithm, std.range; - auto r1 = iota(500).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); - auto r2 = iota(500, 1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); + import core.stdc.tgmath, std.algorithm, std.range; + auto r1 = iota(500.0).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); + auto r2 = iota(500.0, 1000.0).map!(a => 1.7.pow(a+1) - 1.7.pow(a)); Summator!double s1 = 0.0, s2 = 0.0; foreach (e; r1) s1 += e; foreach (e; r2) s2 -= e; s1 -= s2; - s1 -= 1.7.pow(1000); + s1 -= 1.7.pow(1000.0); assert(s1.sum() == -1); } @@ -772,7 +761,7 @@ unittest { import std.range; import std.algorithm; - import std.math; + import core.stdc.tgmath; Summator!double summator = 0; @@ -795,10 +784,10 @@ unittest tuple([double.max, double.max*2.^^-53], double.infinity), tuple(iota(1, 1001).map!(a => 1.0/a).array , 7.4854708605503451), tuple(iota(1, 1001).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... - tuple(iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000))]).array , -1.0), + tuple(iota(1000.0).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000.0))]).array , -1.0), tuple(iota(1, 1001).map!(a => 1.0/a).retro.array , 7.4854708605503451), tuple(iota(1, 1001).map!(a => (-1.0)^^a/a).retro.array, -0.69264743055982025), - tuple(iota(1000).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000))]).retro.array , -1.0), + tuple(iota(1000.0).map!(a => 1.7.pow(a+1) - 1.7.pow(a)).chain([-(1.7.pow(1000.0))]).retro.array , -1.0), tuple([double.infinity, -double.infinity, double.nan], double.nan), tuple([double.nan, double.infinity, -double.infinity], double.nan), tuple([double.infinity, double.nan, double.infinity], double.nan), @@ -834,7 +823,7 @@ unittest assert(summator.isNaN() == r.isNaN()); assert(summator.isFinite() == r.isFinite() || r == -double.max && s == -double.infinity || r == double.max && s == double.infinity); assert(summator.isInfinity() == r.isInfinity() || r == -double.max && s == -double.infinity || r == double.max && s == double.infinity); - assert(s == r || nextDown(s) <= r && nextUp(s) >= r || s.isNaN && r.isNaN); + assert(nextDown(s) <= r && nextUp(s) >= r || s.isNaN && r.isNaN); } else {