diff --git a/std/math.d b/std/math.d index bab9d9bc4..32b8eb669 100644 --- a/std/math.d +++ b/std/math.d @@ -155,6 +155,18 @@ else version(D_InlineAsm_X86_64) version = InlineAsm_X86_Any; } +version (X86_64) version = StaticallyHaveSSE; +version (X86) version (OSX) version = StaticallyHaveSSE; + +version (StaticallyHaveSSE) +{ + private enum bool haveSSE = true; +} +else +{ + static import core.cpuid; + private alias haveSSE = core.cpuid.sse; +} version(unittest) { @@ -4406,7 +4418,9 @@ private: UNDERFLOW_MASK = 0x10, OVERFLOW_MASK = 0x08, DIVBYZERO_MASK = 0x04, - INVALID_MASK = 0x01 + INVALID_MASK = 0x01, + + EXCEPTIONS_MASK = 0b11_1111 } // Don't bother about subnormals, they are not supported on most CPUs. // SUBNORMAL_MASK = 0x02; @@ -4453,27 +4467,19 @@ private: private: static uint getIeeeFlags() { - version(D_InlineAsm_X86) + version(InlineAsm_X86_Any) { - asm pure nothrow @nogc + ushort sw; + asm pure nothrow @nogc { fstsw sw; } + + // OR the result with the SSE2 status register (MXCSR). + if (haveSSE) { - fstsw AX; - // NOTE: If compiler supports SSE2, need to OR the result with - // the SSE2 status register. - // Clear all irrelevant bits - and EAX, 0x03D; - } - } - else version(D_InlineAsm_X86_64) - { - asm pure nothrow @nogc - { - fstsw AX; - // NOTE: If compiler supports SSE2, need to OR the result with - // the SSE2 status register. - // Clear all irrelevant bits - and RAX, 0x03D; + uint mxcsr; + asm pure nothrow @nogc { stmxcsr mxcsr; } + return (sw | mxcsr) & EXCEPTIONS_MASK; } + else return sw & EXCEPTIONS_MASK; } else version (SPARC) { @@ -4491,7 +4497,7 @@ private: else assert(0, "Not yet supported"); } - static void resetIeeeFlags() + static void resetIeeeFlags() @nogc { version(InlineAsm_X86_Any) { @@ -4499,6 +4505,15 @@ private: { fnclex; } + + // Also clear exception flags in MXCSR, SSE's control register. + if (haveSSE) + { + uint mxcsr; + asm nothrow @nogc { stmxcsr mxcsr; } + mxcsr &= ~EXCEPTIONS_MASK; + asm nothrow @nogc { ldmxcsr mxcsr; } + } } else { @@ -4575,6 +4590,52 @@ public: assert(ieeeFlags == f); } +@system unittest +{ + import std.meta : AliasSeq; + + static struct Test + { + void delegate() action; + bool function() ieeeCheck; + } + + foreach (T; AliasSeq!(float, double, real)) + { + T x; /* Needs to be here to trick -O. It would optimize away the + calculations if x were local to the function literals. */ + auto tests = [ + Test( + () { x = 1; x += 0.1; }, + () => ieeeFlags.inexact + ), + Test( + () { x = T.min_normal; x /= T.max; }, + () => ieeeFlags.underflow + ), + Test( + () { x = T.max; x += T.max; }, + () => ieeeFlags.overflow + ), + Test( + () { x = 1; x /= 0; }, + () => ieeeFlags.divByZero + ), + Test( + () { x = 0; x /= 0; }, + () => ieeeFlags.invalid + ) + ]; + foreach (test; tests) + { + resetIeeeFlags(); + assert(!test.ieeeCheck()); + test.action(); + assert(test.ieeeCheck()); + } + } +} + version(X86_Any) { version = IeeeFlagsSupport; @@ -4585,7 +4646,7 @@ else version(ARM) } /// Set all of the floating-point status flags to false. -void resetIeeeFlags() { IeeeFlags.resetIeeeFlags(); } +void resetIeeeFlags() @nogc { IeeeFlags.resetIeeeFlags(); } /// Returns: snapshot of the current state of the floating-point status flags @property IeeeFlags ieeeFlags() @@ -4863,15 +4924,7 @@ private: // Clear all pending exceptions static void clearExceptions() @nogc { - version (InlineAsm_X86_Any) - { - asm nothrow @nogc - { - fclex; - } - } - else - assert(0, "Not yet supported"); + resetIeeeFlags(); } // Read from the control register @@ -4907,24 +4960,33 @@ private: { version (InlineAsm_X86_Any) { - version (Win64) + asm nothrow @nogc { - asm nothrow @nogc - { - naked; - mov 8[RSP],RCX; - fclex; - fldcw 8[RSP]; - ret; - } + fclex; + fldcw newState; } - else + + // Also update MXCSR, SSE's control register. + if (haveSSE) { - asm nothrow @nogc - { - fclex; - fldcw newState; - } + uint mxcsr; + asm nothrow @nogc { stmxcsr mxcsr; } + + /* In the FPU control register, rounding mode is in bits 10 and + 11. In MXCSR it's in bits 13 and 14. */ + enum ROUNDING_MASK_SSE = ROUNDING_MASK << 3; + immutable newRoundingModeSSE = (newState & ROUNDING_MASK) << 3; + mxcsr &= ~ROUNDING_MASK_SSE; // delete old rounding mode + mxcsr |= newRoundingModeSSE; // write new rounding mode + + /* In the FPU control register, masks are bits 0 through 5. + In MXCSR they're 7 through 12. */ + enum EXCEPTION_MASK_SSE = EXCEPTION_MASK << 7; + immutable newExceptionMasks = (newState & EXCEPTION_MASK) << 7; + mxcsr &= ~EXCEPTION_MASK_SSE; // delete old masks + mxcsr |= newExceptionMasks; // write new exception masks + + asm nothrow @nogc { ldmxcsr mxcsr; } } } else @@ -4972,6 +5034,46 @@ private: ensureDefaults(); } +@system unittest // rounding +{ + import std.meta : AliasSeq; + + foreach (T; AliasSeq!(float, double, real)) + { + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundUp; + T u = 1; + u += 0.1; + + fpctrl.rounding = FloatingPointControl.roundDown; + T d = 1; + d += 0.1; + + fpctrl.rounding = FloatingPointControl.roundToZero; + T z = 1; + z += 0.1; + + assert(u > d); + assert(z == d); + + fpctrl.rounding = FloatingPointControl.roundUp; + u = -1; + u -= 0.1; + + fpctrl.rounding = FloatingPointControl.roundDown; + d = -1; + d -= 0.1; + + fpctrl.rounding = FloatingPointControl.roundToZero; + z = -1; + z -= 0.1; + + assert(u > d); + assert(z == u); + } +} + /********************************* * Determines if $(D_PARAM x) is NaN. diff --git a/std/regex/internal/backtracking.d b/std/regex/internal/backtracking.d index 3f72e5958..c13e371e2 100644 --- a/std/regex/internal/backtracking.d +++ b/std/regex/internal/backtracking.d @@ -84,7 +84,9 @@ template BacktrackingMatcher(bool CTregex) static size_t stackSize(const ref RegEx re) { - return initialStack*(stateSize + re.ngroup*(Group!DataIndex).sizeof/size_t.sizeof)+1; + size_t itemSize = stateSize + + re.ngroup * (Group!DataIndex).sizeof / size_t.sizeof; + return initialStack * itemSize + 2; } @property bool atStart(){ return index == 0; } @@ -115,7 +117,30 @@ template BacktrackingMatcher(bool CTregex) { auto chunk = mallocArray!(size_t)(stackSize(re)); chunk[0] = cast(size_t)(memory.ptr); - memory = chunk[1..$]; + chunk[1] = lastState; + memory = chunk[2..$]; + lastState = 0; + } + + bool prevStack() + { + // pointer to previous block + size_t* prev = cast(size_t*) memory.ptr[-2]; + if (!prev) + { + // The last segment is freed in RegexMatch + return false; + } + else + { + import core.stdc.stdlib : free; + // memory used in previous block + size_t size = memory.ptr[-1]; + free(memory.ptr-2); + memory = prev[0 .. size]; + lastState = size; + return true; + } } void initExternalMemory(void[] memBlock) @@ -123,8 +148,9 @@ template BacktrackingMatcher(bool CTregex) merge = arrayInChunk!(Trace)(re.hotspotTableSize, memBlock); merge[] = Trace.init; memory = cast(size_t[]) memBlock; - memory[0] = 0; //hidden pointer - memory = memory[1..$]; + memory[0] = 0; // hidden pointer + memory[1] = 0; // used size + memory = memory[2..$]; } void initialize(ref RegEx program, Stream stream, void[] memBlock) @@ -277,6 +303,7 @@ template BacktrackingMatcher(bool CTregex) pc = 0; counter = 0; lastState = 0; + matches[] = Group!DataIndex.init; auto start = s._index; debug(std_regex_matcher) writeln("Try match starting at ", s[index .. s.lastIndex]); @@ -628,6 +655,8 @@ template BacktrackingMatcher(bool CTregex) case IR.LookbehindEnd: case IR.NeglookbehindEnd: case IR.End: + // cleanup stale stack blocks if any + while (prevStack()) {} return re.ir[pc].data; default: debug printBytecode(re.ir[0..$]); @@ -649,23 +678,6 @@ template BacktrackingMatcher(bool CTregex) 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; - else - { - import core.stdc.stdlib : free; - 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) { @@ -709,10 +721,9 @@ template BacktrackingMatcher(bool CTregex) //helper function, saves engine state void pushState(uint pc, uint counter) { - if (stateSize + matches.length > stackAvail) + if (stateSize + 2 * matches.length > stackAvail) { newStack(); - lastState = 0; } *cast(State*)&memory[lastState] = State(index, pc, counter, infiniteNesting); @@ -727,8 +738,8 @@ template BacktrackingMatcher(bool CTregex) //helper function, restores engine state bool popState() { - if (!lastState) - return prevStack(); + if (!lastState && !prevStack()) + return false; lastState -= 2*matches.length; auto pm = cast(size_t[]) matches; pm[] = memory[lastState .. lastState + 2 * matches.length]; @@ -850,7 +861,6 @@ struct CtContext if (stackAvail < $$*(Group!(DataIndex)).sizeof/size_t.sizeof + $$) { newStack(); - lastState = 0; }", match - reserved, cast(int) counter + 2); if (match < total_matches) text ~= ctSub(" @@ -1439,6 +1449,7 @@ struct CtContext pc = 0; counter = 0; lastState = 0; + matches[] = Group!DataIndex.init; auto start = s._index;`; r ~= ` goto StartLoop; @@ -1467,6 +1478,8 @@ struct CtContext default: assert(0); } + // cleanup stale stack blocks + while (prevStack()) {} return true; } `; diff --git a/std/regex/internal/shiftor.d b/std/regex/internal/shiftor.d deleted file mode 100644 index a438c5b6b..000000000 --- a/std/regex/internal/shiftor.d +++ /dev/null @@ -1,582 +0,0 @@ -/* - ShiftOr is a kickstart engine, a coarse-grained "filter" engine that finds - potential matches to be verified by a full-blown matcher. -*/ -module std.regex.internal.shiftor; - -package(std.regex): - -import std.regex.internal.ir; -import std.range.primitives, 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. -*/ -class ShiftOr(Char) : Kickstart!Char -{ -private: -pure: - 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) - { - static import std.algorithm.comparison; - import std.algorithm.searching : countUntil; - import std.conv : text; - import std.range : assumeSorted; - uint[] memory = new uint[256]; - fChar = uint.max; - // FNV-1a flavored hash (uses 32bits at a time) - ulong hash(uint[] tab) - { - ulong h = 0xcbf29ce484222325; - foreach (v; tab) - { - h ^= v; - h *= 0x100000001b3; - } - return h; - } - 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.Bof, IR.Bol, IR.Wordboundary, IR.Notwordboundary: - i += IRL!(IR.Bol); - break; - default: - break L_FindChar; - } - } - table = memory; - table[] = uint.max; - alias MergeTab = bool[ulong]; - // use reasonably complex hash to identify equivalent tables - auto merge = new MergeTab[re.hotspotTableSize]; - 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.length * 2]; - size_t ofs = 0; - foreach (ival; set) - { - 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 (ival; set) - foreach (ch; ival.a .. ival.b) - { - //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: - auto slot = re.ir[t.pc+1].raw+t.counter; - auto val = hash(t.tab); - if (val in merge[slot]) - goto L_StopThread; // merge equivalent - merge[slot][val] = true; - 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: - auto slot = re.ir[t.pc+1].raw+t.counter; - auto val = hash(t.tab); - if (val in merge[slot]) - goto L_StopThread; // merge equivalent - merge[slot][val] = true; - 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.GroupStart, IR.GroupEnd: - t.pc += IRL!(IR.GroupStart); - break; - case IR.Bof, 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)); - n_length = std.algorithm.comparison.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); - } - } - - final @property bool empty() const { return n_length < 3 && fChar == uint.max; } - - final @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) - final @trusted bool search(ref Input!Char s) const - {//@BUG: apparently assumes little endian machines - import std.conv : text; - import core.stdc.string : memchr; - assert(!empty); - auto haystack = s._origin; - uint state = uint.max; - uint limit = 1u<<(n_length - 1u); - auto p = cast(const(ubyte)*)(haystack.ptr+s._index); - 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) - { - s._index = haystack.length; - return false; - } - if ((cast(size_t) p & (Char.sizeof-1)) == orginalAlign) - break; - if (++p == end) - { - s._index = haystack.length; - return false; - } - } - 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)) - { - s._index = (p-cast(ubyte*) haystack.ptr)/Char.sizeof-length; - return true; - } - } - 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)) - { - s._index = (p-cast(ubyte*) haystack.ptr)/Char.sizeof-length; - return true; - } - } - 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 - { - s._index = (p-cast(ubyte*) haystack.ptr)/Char.sizeof-length; - return true; - } - } - } - 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)) - { - s._index += i/Char.sizeof-length; - return true; - } - } - while (i < len) - { - state = (state << 1) | table[p[i++]]; - if (!(state & limit)) - { - s._index += i/Char.sizeof-length; - return true; - } - state = (state << 1) | table[p[i++]]; - if (!(state & limit)) - { - s._index += i/Char.sizeof-length; - return true; - } - debug(std_regex_search) writefln("State: %32b", state); - } - } - } - s._index = haystack.length; - return false; - } - - final @trusted bool match(ref Input!Char s) const - { - //TODO: stub - return false; - } - - @system debug static void dump(uint[] table) - {//@@@BUG@@@ writef(ln) is @system - import std.stdio : writefln; - for (size_t i = 0; i < table.length; i += 4) - { - debug writefln("%32b %32b %32b %32b",table[i], table[i+1], table[i+2], table[i+3]); - } - } -} - -@safe unittest -{ - import std.conv, std.regex; - auto shiftOrLength(C)(const(C)[] pat, uint length) - { - auto r = regex(pat, "s"); - auto kick = new ShiftOr!C(r); - assert(kick.length == length, text(C.stringof, " == ", kick.length)); - return kick; - } - void searches(C)(const (C)[] source, ShiftOr!C kick, uint[] results...) - { - auto inp = Input!C(source); - foreach (r; results) - { - kick.search(inp); - dchar ch; - size_t idx; - assert(inp._index == r, text(inp._index, " vs ", r)); - inp.nextChar(ch, idx); - } - } - - - foreach (i, Char; AliasSeq!(char, wchar, dchar)) - { - alias String = immutable(Char)[]; - shiftOrLength(`abc`.to!String, 3); - shiftOrLength(`abc$`.to!String, 3); - shiftOrLength(`(abc){2}a+`.to!String, 7); - shiftOrLength(`\b(a{2}b{3}){2,4}`.to!String, 10); - shiftOrLength(`\ba{2}c\bxyz`.to!String, 6); - auto kick = shiftOrLength(`\ba{2}c\b`.to!String, 3); - auto inp = Input!Char("aabaacaa"); - assert(kick.search(inp)); - assert(inp._index == 3, text(Char.stringof," == ", kick.length)); - dchar ch; - size_t idx; - inp.nextChar(ch, idx); - assert(!kick.search(inp)); - assert(inp._index == 8, text(Char.stringof," == ", kick.length)); - } - - foreach (i, Char; AliasSeq!(char, wchar, dchar)) - { - alias String = immutable(Char)[]; - auto kick = shiftOrLength(`abc[a-z]`.to!String, 4); - searches("abbabca".to!String, kick, 3); - kick = shiftOrLength(`(axx|bdx|cdy)`.to!String, 3); - searches("abdcdxabax".to!String, kick, 3); - - shiftOrLength(`...`.to!String, 0); - kick = shiftOrLength(`a(b{1,2}|c{1,2})x`.to!String, 3); - searches("ababx".to!String, kick, 2); - searches("abaacba".to!String, kick, 3); //expected inexact - } - -} - diff --git a/std/regex/internal/tests.d b/std/regex/internal/tests.d index 14613dd71..ccfb80c7d 100644 --- a/std/regex/internal/tests.d +++ b/std/regex/internal/tests.d @@ -1050,9 +1050,35 @@ alias Sequence(int B, int E) = staticIota!(B, E); assertThrown(regex("(?#...")); } +// bugzilla 17075 +@safe unittest +{ + enum titlePattern = `