diff --git a/appveyor.yml b/appveyor.yml index 6ad8ffe0..e6809565 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,7 +8,7 @@ environment: # DVersion: 2.080.0 # arch: x86 - DC: ldc - DVersion: '1.16.0-beta2' + DVersion: '1.18.0' arch: x64 matrix: diff --git a/doc/Makefile b/doc/Makefile index e90e4f26..639c6558 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -28,7 +28,18 @@ ARTWORK_DIR=$(DOC_SOURCE_DIR)/artwork # packages and their modules. MIR_PACKAGES = mir mir/rc mir/ndslice mir/ndslice/connect mir/math mir/math/func mir/array mir/interpolate mir/graph mir/combinatorics mir/container mir/algorithm -PACKAGE_mir = range series numeric type_info small_string small_array polynomial +PACKAGE_mir = \ + range \ + series \ + numeric \ + type_info \ + small_string \ + small_array \ + polynomial \ + format\ + parse\ + appender\ + exception\ PACKAGE_mir_algorithm = iteration setops PACKAGE_mir_array = allocation diff --git a/dub.sdl b/dub.sdl index 226e26e4..064d3f71 100644 --- a/dub.sdl +++ b/dub.sdl @@ -20,9 +20,11 @@ buildType "unittest-release" { versions "mir_test" } +dflags "-preview=dip1008" + configuration "default" { } configuration "dips" { - dflags "-preview=dip1000" "-preview=dip1008" + dflags "-preview=dip1000" } diff --git a/index.d b/index.d index 871533b7..b24d73a3 100644 --- a/index.d +++ b/index.d @@ -28,6 +28,7 @@ $(BOOKTABLE , $(LEADINGROW Containers) $(TR $(TDNW $(MREF mir,series)★) $(TD Generic series suitable for time-series or semi-immutable ordered maps with CPU cache friendly binary search.)) $(TR $(TDNW $(MREF mir,container,binaryheap)★) $(TD Mir & BetterC rework of Phobos.)) + $(TR $(TDNW $(MREF mir,appender)) $(TD Scoped Buffer.)) $(LEADINGROW Graphs) $(TR $(TDNW $(MREF mir,graph)) $(TD Basic routines to work with graphs)) $(TR $(TDNW $(MREF mir,graph,tarjan)★) $(TD Tarjan's strongly connected components algorithm)) @@ -38,6 +39,9 @@ $(BOOKTABLE , $(LEADINGROW Interconnection with other languages) $(TR $(TDNW $(MREF mir,ndslice,connect,cpython)) $(TD Utilities for $(HTTPS docs.python.org/3/c-api/buffer.html, Python Buffer Protocol))) $(LEADINGROW Accessories) + $(TR $(TDNW $(MREF mir,exception)) $(TD @nogc MirException with formatting)) + $(TR $(TDNW $(MREF mir,format)) $(TD @nogc Formatting Utilities)) + $(TR $(TDNW $(MREF mir,parse)) $(TD @nogc Parsing Utilities)) $(TR $(TDNW $(MREF mir,small_array)) $(TD Generic Small Arrays)) $(TR $(TDNW $(MREF mir,small_string)) $(TD Generic Small Strings)) $(TR $(TDNW $(MREF mir,array,allocation)) $(TD `std.array` reworked for Mir)) diff --git a/meson.build b/meson.build index f20c8733..d785310e 100644 --- a/meson.build +++ b/meson.build @@ -18,10 +18,14 @@ required_deps = [mir_core_dep] mir_algorithm_src = [ 'source/mir/algorithm/iteration.d', 'source/mir/algorithm/setops.d', + 'source/mir/appender.d', 'source/mir/array/allocation.d', 'source/mir/combinatorics/package.d', 'source/mir/container/binaryheap.d', 'source/mir/cpp_export/numeric.d', + 'source/mir/exception.d', + 'source/mir/format_impl.d', + 'source/mir/format.d', 'source/mir/graph/package.d', 'source/mir/graph/tarjan.d', 'source/mir/interpolate/constant.d', @@ -52,6 +56,7 @@ mir_algorithm_src = [ 'source/mir/ndslice/topology.d', 'source/mir/ndslice/traits.d', 'source/mir/numeric.d', + 'source/mir/parse.d', 'source/mir/polynomial.d', 'source/mir/range.d', 'source/mir/rc/array.d', diff --git a/source/mir/algorithm/setops.d b/source/mir/algorithm/setops.d index edc8382b..b7f27bbc 100644 --- a/source/mir/algorithm/setops.d +++ b/source/mir/algorithm/setops.d @@ -8,7 +8,11 @@ Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Ya */ module mir.algorithm.setops; +import core.lifetime: move; import mir.functional: naryFun; +import mir.primitives; +import mir.qualifier; +import std.range.primitives: isRandomAccessRange; /** Merges multiple sets. The input sets are passed as a @@ -43,6 +47,7 @@ want to pass a duplicate to $(D MultiwayMerge) (and perhaps cache the duplicate in between calls). */ struct MultiwayMerge(alias less, RangeOfRanges) + if (isRandomAccessRange!RangeOfRanges) { import mir.primitives; import mir.container.binaryheap; @@ -66,12 +71,22 @@ struct MultiwayMerge(alias less, RangeOfRanges) /// this(RangeOfRanges ror) { - import std.algorithm.mutation : remove, SwapStrategy; - // Preemptively get rid of all empty ranges in the input // No need for stability either + auto temp = ror.lightScope; + for (;!temp.empty;) + { + if (!temp.empty) + { + temp.popFront; + continue; + } + move(temp.back, temp.front); + temp.popBack; + ror.popBack; + } //Build the heap across the range - _heap = typeof(_heap)(ror.remove!("a.empty", SwapStrategy.unstable)); + _heap = typeof(_heap)(ror.move); } /// @@ -100,7 +115,7 @@ MultiwayMerge!(naryFun!less, RangeOfRanges) multiwayMerge (alias less = "a < b", RangeOfRanges) (RangeOfRanges ror) { - return typeof(return)(ror); + return typeof(return)(move(ror)); } /// @@ -152,11 +167,11 @@ auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) import mir.functional: not; import mir.algorithm.iteration : Uniq; - return Uniq!(not!less, typeof(multiwayMerge!less(ror)))(multiwayMerge!less(ror)); + return Uniq!(not!less, typeof(multiwayMerge!less(ror)))(multiwayMerge!less(move(ror))); } /// -@system version(mir_test) unittest +@safe version(mir_test) unittest { import std.algorithm.comparison : equal; @@ -199,10 +214,20 @@ pragma(inline, false) size_t unionLength(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) { size_t length; - auto u = ror.multiwayUnion!less; + auto u = move(ror).multiwayUnion!less; if (!u.empty) do { length++; u.popFront; } while(!u.empty); return length; } + +/++ ++/ +auto rcunion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) +{ + import mir.rc.array; + auto length = unionLength!less(ror.lightScope); + auto u = multiwayUnion!less(ror.move); + +} diff --git a/source/mir/appender.d b/source/mir/appender.d new file mode 100644 index 00000000..0580e0c6 --- /dev/null +++ b/source/mir/appender.d @@ -0,0 +1,236 @@ +/++ +$(H1 Scoped Buffer) + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: Ilya Yaroshenko ++/ +module mir.appender; + +// import std.traits: isAssignable, hasElaborateDestructorhasElaborateCopyConstructor, hasElaborateAssign; + +package void _mir_destroy(T)(T[] ar) +{ + static if (__traits(hasMember, T, "__xdtor")) + foreach (ref e; ar) + static if (__traits(isSame, T, __traits(parent, e.__xdtor))) + { + pragma(inline, false) + e.__xdtor(); + } +} + +private extern(C) @system nothrow @nogc pure void* memcpy(scope void* s1, scope const void* s2, size_t n); + + +/// +struct ScopedBuffer(T, size_t bytes = 4096) + if (bytes) +{ + import std.traits: Unqual, isIterable, hasElaborateAssign, isAssignable, isArray; + import mir.primitives: hasLength; + import mir.conv: emplaceRef; + + private enum size_t _bufferLength = bytes / T.sizeof + (bytes % T.sizeof != 0); + private T[] _buffer; + private size_t _currentLength; + private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload = void; + + private ref T[_bufferLength] _scopeBuffer() @trusted scope + { + return *cast(T[_bufferLength]*)&_scopeBufferPayload; + } + + private T[] prepare(size_t n) @trusted scope + { + import mir.internal.memory: realloc, malloc; + _currentLength += n; + if (_buffer.length == 0) + { + if (_currentLength <= _bufferLength) + { + return _scopeBuffer[0 .. _currentLength]; + } + else + { + auto newLen = _currentLength << 1; + _buffer = (cast(T*)malloc(T.sizeof * newLen))[0 .. newLen]; + memcpy(cast(void*)_buffer.ptr, _scopeBuffer.ptr, T.sizeof * (_currentLength - n)); + } + } + else + if (_currentLength > _buffer.length) + { + auto newLen = _currentLength << 1; + _buffer = (cast(T*)realloc(cast(void*)_buffer.ptr, T.sizeof * newLen))[0 .. newLen]; + } + return _buffer[0 .. _currentLength]; + } + + static if (isAssignable!(T, const T)) + private alias R = const T; + else + private alias R = T; + + /// + @disable this(this); + + /// + ~this() + { + import mir.internal.memory: free; + data._mir_destroy; + (() @trusted => free(cast(void*)_buffer.ptr))(); + } + + /// + void popBackN(size_t n) + { + sizediff_t t = _currentLength - n; + if (t < 0) + assert(0, "ScopedBffer.popBackN: n is too large."); + import mir.exception; + data[t .. _currentLength]._mir_destroy; + _currentLength = t; + } + + /// + void put(R e) @safe scope + { + auto cl = _currentLength; + prepare(1); + emplaceRef!(Unqual!T)(data[cl], e); + } + + static if (T.sizeof > 8 || hasElaborateAssign!T) + /// + void put(ref R e) scope + { + auto cl = _currentLength; + auto d = prepare(1); + emplaceRef!(Unqual!T)(d[cl], e); + } + + static if (!hasElaborateAssign!T && isAssignable!(T, const T)) + /// + void put(scope const(T)[] e) scope + { + auto cl = _currentLength; + auto d = prepare(e.length); + auto len = e.length * T.sizeof; + if (!__ctfe) + (()@trusted=>memcpy(cast(void*)(d.ptr + cl), e.ptr, len))(); + else + (()@trusted { (d.ptr + cl)[0 .. len] = e[0 .. len]; })(); + } + + static if (!hasElaborateAssign!T && !isAssignable!(T, const T)) + /// + void put()(scope T[] e) scope + { + auto cl = _currentLength; + auto d = prepare(e.length); + auto len = e.length * T.sizeof; + if (!__ctfe) + (()@trusted=>memcpy(cast(void*)(d.ptr + cl), e.ptr, len))(); + else + (()@trusted { + foreach(i; 0 .. cl) + d[i].emplaceRef!T(e[i]); + })(); + } + + /// + void put(Iterable)(Iterable range) scope + if (isIterable!Iterable && !isArray!Iterable) + { + static if (hasLength!Iterable) + { + auto cl = _currentLength; + auto d = prepare(range.length); + static if (is(Iterable : R[]) && !hasElaborateAssign!T) + { + auto len = range.length * T.sizeof; + if (!__ctfe) + (()@trusted=>memcpy(d.ptr + cl, e.ptr, len))(); + else + (()@trusted { (d.ptr + cl)[0 .. len] = e[0 .. len]; })(); + } + else + { + foreach(ref e; range) + emplaceRef!(Unqual!T)(d[cl++], e); + assert(_currentLength == cl); + } + } + else + { + foreach(ref e; range) + put(e); + } + } + + /// + void reset() scope nothrow + { + this.__dtor; + _currentLength = 0; + _buffer = null; + } + + /// + T[] data() @property @safe scope + { + return _buffer.length ? _buffer[0 .. _currentLength] : _scopeBuffer[0 .. _currentLength]; + } + + /++ + Copies data into an array of the same length using `memcpy` C routine. + Shrinks the length to `0`. + +/ + void moveDataAndEmplaceTo(T[] array) @system + in { + assert(array.length == _currentLength); + } + body { + memcpy(cast(void*)array.ptr, data.ptr, _currentLength * T.sizeof); + _currentLength = 0; + } +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + ScopedBuffer!char buf; + buf.put('c'); + buf.put("str"); + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} + +/// immutable +@safe pure nothrow @nogc +version (mir_test) unittest +{ + ScopedBuffer!(immutable char) buf; + buf.put('c'); + buf.put("str"); + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} + +@safe pure nothrow @nogc +version (mir_test) unittest +{ + ScopedBuffer!(char, 3) buf; + buf.put('c'); + buf.put("str"); + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} diff --git a/source/mir/array/allocation.d b/source/mir/array/allocation.d index 48729ff3..eabcceb2 100644 --- a/source/mir/array/allocation.d +++ b/source/mir/array/allocation.d @@ -113,8 +113,8 @@ if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isStaticAr } else { - import std.array: appender; - auto a = appender!(E[])(); + import mir.appender: ScopedBuffer; + ScopedBuffer!E a; static if (isInputRange!Range) for (; !r.empty; r.popFront) a.put(r.front); @@ -125,7 +125,7 @@ if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isStaticAr else foreach (e; r) a.put(e); - return a.data; + return .array(a.data); } } diff --git a/source/mir/container/binaryheap.d b/source/mir/container/binaryheap.d index 38902afd..27699e48 100644 --- a/source/mir/container/binaryheap.d +++ b/source/mir/container/binaryheap.d @@ -63,7 +63,7 @@ insertBack), the $(D BinaryHeap) may grow by adding elements to the container. +/ struct BinaryHeap(alias less = "a < b", Store) -if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) +if (isRandomAccessRange!Store || isRandomAccessRange!(typeof(Store.init[]))) { import mir.utility : min; import mir.functional : naryFun; diff --git a/source/mir/exception.d b/source/mir/exception.d new file mode 100644 index 00000000..49efdb96 --- /dev/null +++ b/source/mir/exception.d @@ -0,0 +1,285 @@ +/++ +`@nogc` exceptions and errors definitions. + +Most of API Required DIP1008. ++/ +module mir.exception; + +/++ ++/ +class MirException : Exception +{ + /// + mixin MirThrowableImpl; +} + +/// Generic style +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.exception; + try throw new MirException("Hi D", 2, "!"); + catch(Exception e) assert(e.msg == "Hi D2!"); +} + +/// C++ style +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.exception; + import mir.format; + try throw new MirException(stringBuf() << "Hi D" << 2 << "!" << getData); + catch(Exception e) assert(e.msg == "Hi D2!"); +} + +/// Low-level style +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.exception; + import mir.format; + stringBuf buf; + try throw new MirException(buf.print( "Hi D", 2, "!").data); + catch(Exception e) assert(e.msg == "Hi D2!"); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + @safe pure nothrow @nogc + bool func(scope const(char)[] msg) + { + /// scope messages are copied + try throw new MirException(msg); + catch(Exception e) assert(e.msg == msg); + + /// immutable strings are not copied + static immutable char[] gmsg = "global msg"; + try throw new MirException(gmsg); + catch(Exception e) assert(e.msg is gmsg); + + return __ctfe; + } + + assert(func("runtime-time check") == 0); + + static assert(func("compile-time check") == 1); +} + +// /// +// auto ref enforce(T, Args...)(scope auto return ref T arg, lazy @nogc Args args, string file = __FILE__, int line = __LINE__) @nogc +// if (Args.length) +// { +// import mir.utility: _expect; +// static if (__traits(compiles, arg !is null)) +// { +// if (_expect(arg !is null, true)) +// return arg; +// } +// else +// { +// if (_expect(cast(bool)arg, true)) +// return arg; +// } +// import mir.format; +// stringBuf buf; +// throw new MirException(buf.print(args).data, file, line); +// } + +// /// +// @safe pure nothrow @nogc +// version (mir_test) unittest +// { +// import mir.exception; +// try enforce(false, "Hi D", 2, "!"); +// catch(Exception e) assert(e.msg == "Hi D2!"); +// } + +// /// +// auto ref enforce(T)(scope auto return ref T arg, lazy scope const(char)[] msg, string file = __FILE__, int line = __LINE__) @nogc +// { +// import mir.functional: forward; +// import mir.utility: _expect; +// static if (__traits(compiles, arg !is null)) +// { +// if (_expect(arg !is null, true)) +// return forward!arg[0]; +// } +// else +// { +// if (_expect(cast(bool)arg, true)) +// return forward!arg[0]; +// } +// throw new MirException(msg, file, line); +// } + +// /// +// @safe pure nothrow @nogc +// version (mir_test) unittest +// { +// import mir.exception; +// try enforce(false, "Msg"); +// catch(Exception e) assert(e.msg == "Msg"); +// } + +/// +auto ref enforce(string fmt, string file = __FILE__, int line = __LINE__, Expr)(scope auto return ref Expr arg) @trusted +{ + import mir.functional: forward; + import mir.utility: _expect; + static if (__traits(compiles, arg !is null)) + { + if (_expect(arg !is null, true)) + return forward!arg[0]; + } + else + { + if (_expect(cast(bool)arg, true)) + return forward!arg[0]; + } + static immutable exception = new Exception(fmt, file, line); + throw exception; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.exception; + try enforce!"Msg"(false); + catch(Exception e) assert(e.msg == "Msg"); +} + +/++ ++/ +class MirError : Error +{ + /// + mixin MirThrowableImpl; +} + +/// +@system pure nothrow @nogc +version (mir_test) unittest +{ + @system pure nothrow @nogc + bool func(scope const(char)[] msg) + { + /// scope messages are copied + try throw new MirException(msg); + catch(Exception e) assert(e.msg == msg); + + /// immutable strings are not copied + static immutable char[] gmsg = "global msg"; + try throw new MirError(gmsg); + catch(Error e) assert(e.msg is gmsg); + + return __ctfe; + } + + assert(func("runtime-time check") == 0); + + static assert(func("compile-time check") == 1); +} + +/++ ++/ +mixin template MirThrowableImpl() +{ + private bool _global; + private char[maxMsgLen] _payload = void; + + /++ + Params: + msg = message. No-scope `msg` is assumed to have the same lifetime as the throwable. scope strings are copied to internal buffer. + file = file name, zero terminated global string + line = line number + nextInChain = next exception in the chain (optional) + +/ + @nogc @safe pure nothrow this(scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super((() @trusted => cast(immutable) initilizePayload(_payload, msg))(), file, line, nextInChain); + } + + /// ditto + @nogc @safe pure nothrow this(scope const(char)[] msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + this(msg, file, line, nextInChain); + } + + /// ditto + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + this._global = true; + super(msg, file, line, nextInChain); + } + + /// ditto + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + this._global = true; + super(msg, file, line, nextInChain); + } + + /// + ~this() @trusted + { + import mir.internal.memory: free; + if (!_global && msg.ptr != _payload.ptr) + free(cast(void*)msg.ptr); + } + + /++ + Generic multiargument overload. + Constructs a string using the `print` function. + +/ + @nogc @safe pure nothrow this(Args...)(scope auto ref Args args, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + if (Args.length > 1) + { + import mir.format; + stringBuf buf; + this(buf.print(args).data, file, line, nextInChain); + } +} + +private enum maxMsgLen = 447; + +pragma(inline, false) +pure nothrow @nogc @safe +private const(char)[] initilizePayload(ref return char[maxMsgLen] payload, scope const(char)[] msg) +{ + import mir.internal.memory: malloc; + import core.stdc.string: memcpy; + if (msg.length > payload.length) + { + if (auto ret = (() @trusted + { + if (__ctfe) + return null; + if (auto ptr = malloc(msg.length)) + { + memcpy(ptr, msg.ptr, msg.length); + return cast(const(char)[]) ptr[0 .. msg.length]; + } + return null; + })()) + return ret; + msg = msg[0 .. payload.length]; + // remove tail UTF-8 symbol chunk if any + uint c = msg[$-1]; + if (c > 0b_0111_1111) + { + do { + c = msg[$-1]; + msg = msg[0 .. $ - 1]; + } + while (msg.length && c < 0b_1100_0000); + } + } + if (__ctfe) + payload[][0 .. msg.length] = msg; + else + (() @trusted => memcpy(payload.ptr, msg.ptr, msg.length))(); + return payload[0 .. msg.length]; +} diff --git a/source/mir/format.d b/source/mir/format.d new file mode 100644 index 00000000..cb295a34 --- /dev/null +++ b/source/mir/format.d @@ -0,0 +1,794 @@ +/++ +$(H1 @nogc Formatting Utilities) + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: Ilya Yaroshenko ++/ +module mir.format; + +import std.traits; + +import mir.format_impl; + +/// +struct GetData {} + +/// +enum getData = GetData(); + +/++ ++/ +struct _stringBuf(C) +{ + import mir.appender: ScopedBuffer; + + /// + ScopedBuffer!C buffer; + + /// + alias buffer this; + + /// + mixin StreamFormatOp!C; +} + +///ditto +alias stringBuf = _stringBuf!char; +///ditto +alias wstringBuf = _stringBuf!wchar; +///ditto +alias dstringBuf = _stringBuf!dchar; + +/++ ++/ +mixin template StreamFormatOp(C) +{ + /// + ref typeof(this) opBinary(string op : "<<", T)(scope ref const T c) scope + { + return print!C(this, c); + } + + /// + ref typeof(this) opBinary(string op : "<<", T)(const T c) scope + { + return print!C(this, c); + } + + /// ditto + const(C)[] opBinary(string op : "<<", T : GetData)(const T c) scope + { + return buffer.data; + } +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto name = "D"; + auto ver = 2.0; + assert(stringBuf() << "Hi " << name << ver << "!\n" << getData == "Hi D2!\n"); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto name = "D"w; + auto ver = 2; + assert(wstringBuf() << "Hi "w << name << ver << "!\n"w << getData == "Hi D2!\n"w); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto name = "D"d; + auto ver = 2; + assert(dstringBuf() << "Hi "d << name << ver << "!\n"d << getData == "Hi D2!\n"); +} + +@safe pure nothrow @nogc +version (mir_test) unittest +{ + assert(stringBuf() << -1234567890 << getData == "-1234567890"); +} + +// 16-bytes +/// C's compatible format specifier. +struct FormatSpec +{ + /// + bool dash; + /// + bool plus; + /// + bool space; + /// + bool hash; + /// + bool zero; + /// + char format = 's'; + /// + char separator = '\0'; + /// + ubyte unitSize; + /// + int width; + /// + int precision = -1; +} + +/++ ++/ +enum SwitchLU : bool +{ + /// + lower, + /// + upper, +} + +/++ ++/ +struct FormattedFloating(T) + if(is(T == float) || is(T == double) || is(T == real)) +{ + /// + T value; + /// + FormatSpec spec; + + /// + void toString(C = char, W)(scope ref W w) scope const + { + C[512] buf = void; + auto n = printFloatingPoint(value, spec, buf); + w.put(buf[0 .. n]); + } +} + +/// ditto +FormattedFloating!T withFormat(T)(const T value, FormatSpec spec) +{ + version(LDC) pragma(inline); + return typeof(return)(value, spec); +} + +/++ ++/ +struct HexAddress(T) + if (isUnsigned!T && !is(T == enum)) +{ + /// + T value; + /// + SwitchLU switchLU = SwitchLU.upper; + + /// + void toString(C = char, W)(scope ref W w) scope const + { + enum N = T.sizeof * 2; + static if(isFastBuffer!W) + { + w.advance(printHexAddress(value, w.getBuffer(N).getStaticBuf!N, cast(bool) switchLU)); + } + else + { + C[N] buf = void; + printHexAddress(value, buf, cast(bool) switchLU); + w.put(buf[]); + } + } +} + +/++ +Note: Non-ASCII Unicode characters are encoded as sequence of \xXX bytes. This may be fixed in the future. ++/ +pragma(inline, false) +ref W printEscaped(C = char, W)(scope return ref W w, scope const(char)[] str) +{ + // TODO: replace with Mir implementation. + import std.uni: isGraphical; + w.put('\"'); + foreach (char c; str[]) + { + if (c >= 0x20) + { + if (c < 0x7F) + { + if (c == '\"' || c == '\\') + { + L: + w.put('\\'); + } + w.put(c); + } + else + { + M: + printStaticStringInternal!(C, "\\x")(w); + print!C(w, HexAddress!ubyte(cast(ubyte)c)); + } + } + else + { + switch(c) + { + case '\n': c = 'n'; goto L; + case '\r': c = 'r'; goto L; + case '\t': c = 't'; goto L; + case '\a': c = 'a'; goto L; + case '\b': c = 'b'; goto L; + case '\f': c = 'f'; goto L; + case '\v': c = 'v'; goto L; + case '\0': c = '0'; goto L; + default: goto M; + } + } + } + w.put('\"'); + return w; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + assert(w.printEscaped("Hi\t" ~ `"@nogc"`).data == `"Hi\t\"@nogc\""`, w.data); + w.reset; + assert(w.printEscaped("\xF3").data == `"\xF3"`, w.data); +} + +/// +ref W printElement(C = char, W, T)(scope return ref W w, scope auto ref const T c) +{ + static if (isSomeString!T) + { + return printEscaped!C(w, c); + } + else + { + return print!C(w, c); + } +} + +/++ +Multiargument overload. ++/ +ref W print(C = char, W, Args...)(scope return ref W w, scope auto ref const Args args) + if (Args.length > 1) +{ + foreach(i, ref c; args) + static if (i < Args.length - 1) + print!C(w, c); + else + return print!C(w, c); +} + +/// +ref W print(C = char, W, T)(scope return ref W w, const T c) + if (is(T == enum)) +{ + static assert(!is(OriginalType!T == enum)); + string s; + S: switch (c) + { + static foreach(member; __traits(allMembers, T)) + { + case __traits(getMember, T, member): + s = member; + break S; + } + default: + static immutable C[] str = T.stringof; + w.put(str[]); + w.put('('); + print(w, cast(OriginalType!T) c); + w.put(')'); + return w; + } + w.put(s); + return w; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + enum Flag + { + no, + yes, + } + + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + w.print(Flag.yes); + assert(w.data == "yes", w.data); +} + +/// ditto +ref W print(C = char, W)(scope return ref W w, bool c) +{ + enum N = 5; + static if(isFastBuffer!W) + { + w.advance(printBoolean(c, w.getBuffer(N).getStaticBuf!N)); + } + else + { + C[N] buf = void; + auto n = printBoolean(c, buf); + w.put(buf[0 .. n]); + } + return w; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + assert(w.print(true).data == `true`, w.data); + w.reset; + assert(w.print(false).data == `false`, w.data); +} + +/// ditto +pragma(inline, false) +ref W print(C = char, W, V, K)(scope return ref W w, scope const V[K] c) +{ + enum C left = '['; + enum C right = ']'; + enum C[2] sep = ", "; + enum C[2] mid = ": "; + w.put(left); + bool first = true; + foreach (ref key, ref value; c) + { + if (!first) + printStaticStringInternal!(C, sep)(w); + first = false; + printElement!C(w, key); + printStaticStringInternal!(C, mid)(w); + printElement!C(w, value); + } + w.put(right); + return w; +} + +/// +@safe pure +version (mir_test) unittest +{ + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + w.print(["a": 1, "b": 2]); + assert(w.data == `["a": 1, "b": 2]` || w.data == `["b": 2, "a": 1]`, w.data); +} + +/// ditto +pragma(inline, false) +ref W print(C = char, W, T)(scope return ref W w, scope const(T)[] c) + if (!isSomeChar!T) +{ + enum C left = '['; + enum C right = ']'; + enum C[2] sep = ", "; + w.put(left); + bool first = true; + foreach (ref e; c) + { + if (!first) + printStaticStringInternal!(C, sep)(w); + first = false; + printElement!C(w, e); + } + w.put(right); + return w; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + string[2] array = ["a\ta", "b"]; + assert(w.print(array[]).data == `["a\ta", "b"]`, w.data); +} + +/// ditto +pragma(inline, false) +ref W print(C = char, W)(scope return ref W w, char c) +{ + w.put('\''); + if (c >= 0x20) + { + if (c < 0x7F) + { + if (c == '\'' || c == '\\') + { + L: + w.put('\\'); + } + w.put(c); + } + else + { + M: + printStaticStringInternal!(C, "\\x")(w); + print!C(w, HexAddress!ubyte(cast(ubyte)c)); + } + } + else + { + switch(c) + { + case '\n': c = 'n'; goto L; + case '\r': c = 'r'; goto L; + case '\t': c = 't'; goto L; + case '\a': c = 'a'; goto L; + case '\b': c = 'b'; goto L; + case '\f': c = 'f'; goto L; + case '\v': c = 'v'; goto L; + case '\0': c = '0'; goto L; + default: goto M; + } + } + w.put('\''); + return w; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + assert(w + .print('\n') + .print('\'') + .print('a') + .print('\xF4') + .data == `'\n''\'''a''\xF4'`); +} + +/// ditto +ref W print(C = char, W)(scope return ref W w, scope const(C)[] c) + if (isSomeChar!C) +{ + w.put(c); + return w; +} + +/// ditto +ref W print(C = char, W, I)(scope return ref W w, const I c) + if (isIntegral!I && !is(I == enum)) +{ + static if (I.sizeof == 16) + enum N = 39; + else + static if (I.sizeof == 8) + enum N = 20; + else + enum N = 10; + C[N + !__traits(isUnsigned, I)] buf = void; + static if (__traits(isUnsigned, I)) + auto n = printUnsignedToTail(c, buf); + else + auto n = printSignedToTail(c, buf); + w.put(buf[$ - n .. $]); + return w; +} + +/// ditto +ref W print(C = char, W, T)(scope return ref W w, const T c) + if(is(T == float) || is(T == double) || is(T == real)) +{ + auto ff = FormattedFloating!T(c); + return print!C(w, ff); +} + +/// ditto +pragma(inline, false) +ref W print(C = char, W, T)(scope return ref W w, scope ref const T c) + if (is(T == struct) || is(T == union)) +{ + static if (__traits(hasMember, T, "toString")) + { + static if (is(typeof(c.toString!C(w)))) + c.toString!C(w); + else + static if (is(typeof(c.toString(w)))) + c.toString(w); + else + static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) + c.toString((scope const(C)[] s) { w.put(s); }); + else + static if (is(typeof(w.put(c.toString)))) + w.put(c.toString); + else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing."); + return w; + } + else + static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) + { + scope const(C)[] string_of_c = c; + return print(w, string_of_c); + } + else + static if (hasIterableLightConst!T) + { + enum C left = '['; + enum C right = ']'; + enum C[2] sep = ", "; + w.put(left); + bool first = true; + foreach (ref e; c.lightConst) + { + if (!first) + printStaticStringInternal!(C, sep)(w); + first = false; + print!C(w, e); + } + w.put(right); + return w; + } + else + { + enum C left = '('; + enum C right = ')'; + enum C[2] sep = ", "; + w.put(left); + foreach (i, ref e; c.tupleof) + { + static if (i) + printStaticStringInternal!(C, sep)(w); + print!C(w, e); + } + w.put(right); + return w; + } +} + +/// ditto +// FUTURE: remove it +pragma(inline, false) +ref W print(C = char, W, T)(scope return ref W w, scope const T c) + if (is(T == struct) || is(T == union)) +{ + return print!(C, W, T)(w, c); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + static struct A { scope const void toString(C, W)(scope ref W w) { w.put(C('a')); } } + static struct S { scope const void toString(W)(scope ref W w) { w.put("s"); } } + static struct D { scope const void toString(Dg)(scope Dg sink) { sink("d"); } } + static struct F { scope const const(char)[] toString()() return { return "f"; } } + static struct G { const(char)[] s = "g"; alias s this; } + + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + assert(stringBuf() << A() << S() << D() << F() << G() << getData == "asdfg"); +} + +/// ditto +pragma(inline, false) +ref W print(C = char, W, T)(scope return ref W w, scope const T c) + if (is(T == class) || is(T == interface)) +{ + static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; })) + { + if (c is null) + printStaticStringInternal!(C, "null")(w); + else + static if (is(typeof(c.toString!C(w)))) + c.toString!C(w); + else + static if (is(typeof(c.toString(w)))) + c.toString(w); + else + static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) + c.toString((scope const(C)[] s) { w.put(s); }); + else + static if (is(typeof(w.put(c.toString)))) + w.put(c.toString); + else + static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) + { + scope const(C)[] string_of_c = c; + return print(w, string_of_c); + } + else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing."); + } + else + static if (hasIterableLightConst!T) + { + enum C left = '['; + enum C right = ']'; + enum C[2] sep = ", "; + w.put(left); + bool first = true; + foreach (ref e; c.lightConst) + { + if (!first) + printStaticStringInternal!(C, sep)(w); + first = false; + print!C(w, e); + } + w.put(right); + } + else + { + w.put(T.stringof); + } + return w; +} + +/// +@safe pure nothrow +version (mir_test) unittest +{ + static class A { scope const void toString(C, W)(scope ref W w) { w.put(C('a')); } } + static class S { scope const void toString(W)(scope ref W w) { w.put("s"); } } + static class D { scope const void toString(Dg)(scope Dg sink) { sink("d"); } } + static class F { scope const const(char)[] toString()() return { return "f"; } } + static class G { const(char)[] s = "g"; alias s this; } + + import mir.appender: ScopedBuffer; + ScopedBuffer!char w; + assert(stringBuf() << new A() << new S() << new D() << new F() << new G() << getData == "asdfg"); +} + +private template hasIterableLightConst(T) +{ + static if (__traits(hasMember, T, "lightConst")) + { + enum hasIterableLightConst = isIterable!(ReturnType!((const T t) => t.lightConst)); + } + else + { + enum hasIterableLightConst = false; + } +} + +private ref W printStaticStringInternal(C, immutable(C)[] c, W)(scope return ref W w) + if (C.sizeof * c.length <= 512) +{ + static if (isFastBuffer!W) + { + printStaticString!c(w.getBuffer(c.length).getStaticBuf!(c.length)); + w.advance(c.length); + } + else + static if (c.length <= 4) + { + static foreach(i; 0 .. c.length) + w.put(c[i]); + } + else + { + w.put(c[]); + } + return w; +} + +private @trusted ref C[N] getStaticBuf(size_t N, C)(scope return ref C[] buf) +{ + assert(buf.length >= N); + return buf.ptr[0 .. N]; +} + +template isFastBuffer(W) +{ + enum isFastBuffer = __traits(hasMember, W, "getBuffer") && __traits(hasMember, W, "advance"); +} + +/// +ref W printZeroPad(C = char, W, I)(scope return ref W w, const I c, size_t minimalLength) + if (isIntegral!I && !is(I == enum)) +{ + static if (I.sizeof == 16) + enum N = 39; + else + static if (I.sizeof == 8) + enum N = 20; + else + enum N = 10; + C[N + !__traits(isUnsigned, I)] buf = void; + static if (__traits(isUnsigned, I)) + auto n = printUnsignedToTail(c, buf); + else + auto n = printSignedToTail(c, buf); + sizediff_t zeros = minimalLength - n; + + if (zeros > 0) + { + static if (!__traits(isUnsigned, I)) + { + if (c < 0) + { + n--; + w.put(C('-')); + } + } + do w.put(C('0')); + while(--zeros); + } + w.put(buf[$ - n .. $]); + return w; +} + +/// +version (mir_test) unittest +{ + import mir.appender; + ScopedBuffer!char w; + + w.printZeroPad(-123, 5); + w.put(' '); + w.printZeroPad(123, 5); + + assert(w.data == "-0123 00123", w.data); +} + +/// +size_t printBoolean(C)(bool c, ref C[5] buf) + if(is(C == char) || is(C == wchar) || is(C == dchar)) +{ + version(LDC) pragma(inline, true); + if (c) + { + buf[0] = 't'; + buf[1] = 'r'; + buf[2] = 'u'; + buf[3] = 'e'; + return 4; + } + else + { + buf[0] = 'f'; + buf[1] = 'a'; + buf[2] = 'l'; + buf[3] = 's'; + buf[4] = 'e'; + return 5; + } +} + +/// +size_t printStaticString(string str, C)(scope ref C[str.length] buf) + if((is(C == char) || is(C == wchar) || is(C == dchar)) && (C[str.length]).sizeof <= 512) +{ + version(LDC) pragma(inline, true); + static foreach (i, e; str) buf[i] = e; + return buf.length; +} + +/// ditto +size_t printStaticString(wstring str, C)(scope ref C[str.length] buf) + if((is(C == wchar) || is(C == dchar)) && (C[str.length]).sizeof <= 512) +{ + version(LDC) pragma(inline, true); + static foreach (i, e; str) buf[i] = e; + return buf.length; +} + +/// ditto +size_t printStaticString(dstring str)(scope ref dchar[str.length] buf) + if((dchar[str.length]).sizeof <= 512) +{ + version(LDC) pragma(inline, true); + static foreach (i, e; str) buf[i] = e; + return buf.length; +} diff --git a/source/mir/format_impl.d b/source/mir/format_impl.d new file mode 100644 index 00000000..7cba4517 --- /dev/null +++ b/source/mir/format_impl.d @@ -0,0 +1,438 @@ +/// +module mir.format_impl; + +import mir.format; + +@safe pure @nogc nothrow: + + +size_t printFloatingPointExtend(T, C)(T c, scope ref const FormatSpec spec, scope ref C[512] buf) @trusted +{ + char[512] cbuf = void; + return extendASCII(cbuf[].ptr, buf[].ptr, printFloatingPoint(cast(double)c, spec, cbuf)); +} + +size_t printFloatingPointGen(T)(T c, scope ref const FormatSpec spec, scope ref char[512] buf) @trusted + if(is(T == float) || is(T == double) || is(T == real)) +{ + import mir.math.common: copysign, fabs; + bool neg = copysign(1, c) < 0; + c = fabs(c); + char specFormat = spec.format; + version (CRuntime_Microsoft) + { + if (c != c || c.fabs == c.infinity) + { + size_t i; + char s = void; + if (copysign(1, c) < 0) + s = '-'; + else + if (spec.plus) + s = '+'; + else + if (spec.space) + s = ' '; + else + goto S; + buf[0] = s; + i = 1; + S: + static immutable char[3][2][2] special = [["inf", "INF"], ["nan", "NAN"]]; + auto p = &special[c != c][(specFormat & 0xDF) == specFormat][0]; + buf[i + 0] = p[0]; + buf[i + 1] = p[1]; + buf[i + 2] = p[2]; + return i + 3; + } + } + alias T = double; + static if (is(T == real)) + align(4) char[12] fmt = "%%%%%%*.*gL\0"; + else + align(4) char[12] fmt = "%%%%%%*.*g\0\0"; + + if (specFormat && specFormat != 's' && specFormat != 'g' && specFormat != 'G') + { + assert ( + specFormat == 'e' + || specFormat == 'E' + || specFormat == 'f' + || specFormat == 'F' + || specFormat == 'a' + || specFormat == 'A', "Wrong floating point format specifier."); + fmt[9] = specFormat; + } + uint fmtRevLen = 5; + if (spec.hash) fmt[fmtRevLen--] = '#'; + if (spec.space) fmt[fmtRevLen--] = ' '; + if (spec.zero) fmt[fmtRevLen--] = '0'; + if (spec.plus) fmt[fmtRevLen--] = '+'; + if (spec.dash) fmt[fmtRevLen--] = '-'; + + import core.stdc.stdio : snprintf; + ptrdiff_t res = assumePureSafe(&snprintf)((()@trusted =>buf.ptr)(), buf.length - 1, &fmt[fmtRevLen], spec.width, spec.precision, c); + assert (res >= 0, "snprintf failed to print a floating point number"); + import mir.utility: min; + return res < 0 ? 0 : min(cast(size_t)res, buf.length - 1); +} + +auto assumePureSafe(T)(T t) @trusted + // if (isFunctionPointer!T || isDelegate!T) +{ + import std.traits; + enum attrs = (functionAttributes!T | FunctionAttribute.pure_ | FunctionAttribute.safe) & ~FunctionAttribute.system; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; +} + +////////// FLOATING POINT ////////// + +pragma(inline, false) size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref char[512] buf) +{ + return printFloatingPoint(cast(double)c, spec, buf); +} + +pragma(inline, false) size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref char[512] buf) +{ + return printFloatingPointGen(c, spec, buf); +} + +pragma(inline, false) size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref char[512] buf) +{ + version (CRuntime_Microsoft) + { + return printFloatingPoint(cast(double) c, spec, buf); + } + else + { + pragma(inline, false); + return printFloatingPointGen(c, spec, buf); + } +} + +pragma(inline, false) size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref wchar[512] buf) +{ + return printFloatingPoint(cast(double)c, spec, buf); +} + +pragma(inline, false) size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref wchar[512] buf) +{ + return printFloatingPointExtend(c, spec, buf); +} + +pragma(inline, false) size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref wchar[512] buf) +{ + version (CRuntime_Microsoft) + { + return printFloatingPoint(cast(double) c, spec, buf); + } + else + { + return printFloatingPointExtend(c, spec, buf); + } +} + +pragma(inline, false) size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref dchar[512] buf) +{ + return printFloatingPoint(cast(double)c, spec, buf); +} + +pragma(inline, false) size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref dchar[512] buf) +{ + return printFloatingPointExtend(c, spec, buf); +} + +pragma(inline, false) size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref dchar[512] buf) +{ + version (CRuntime_Microsoft) + { + return printFloatingPoint(cast(double) c, spec, buf); + } + else + { + return printFloatingPointExtend(c, spec, buf); + } +} + +nothrow: + +pragma(inline, false) size_t printHexadecimal(uint c, ref char[8] buf, bool upper) { return printHexadecimalGen!(uint, char)(c, buf, upper); } +pragma(inline, false) size_t printHexadecimal(ulong c, ref char[16] buf, bool upper) { return printHexadecimalGen!(ulong, char)(c, buf, upper); } +static if (is(ucent)) +pragma(inline, false) size_t printHexadecimal(ucent c, ref char[32] buf, bool upper) { return printHexadecimalGen!(ucent, char)(c, buf, upper); } + +pragma(inline, false) size_t printHexadecimal(uint c, ref wchar[8] buf, bool upper) { return printHexadecimalGen!(uint, wchar)(c, buf, upper); } +pragma(inline, false) size_t printHexadecimal(ulong c, ref wchar[16] buf, bool upper) { return printHexadecimalGen!(ulong, wchar)(c, buf, upper); } +static if (is(ucent)) +pragma(inline, false) size_t printHexadecimal(ucent c, ref wchar[32] buf, bool upper) { return printHexadecimalGen!(ucent, wchar)(c, buf, upper); } + +pragma(inline, false) size_t printHexadecimal(uint c, ref dchar[8] buf, bool upper) { return printHexadecimalGen!(uint, dchar)(c, buf, upper); } +pragma(inline, false) size_t printHexadecimal(ulong c, ref dchar[16] buf, bool upper) { return printHexadecimalGen!(ulong, dchar)(c, buf, upper); } +static if (is(ucent)) +pragma(inline, false) size_t printHexadecimal(ucent c, ref dchar[32] buf, bool upper) { return printHexadecimalGen!(ucent, dchar)(c, buf, upper); } + +size_t printHexadecimalGen(T, C)(T c, ref C[T.sizeof * 2] buf, bool upper) @trusted +{ + if (c < 10) + { + buf[0] = cast(char)('0' + c); + return 1; + } + import mir.bitop: ctlz; + immutable hexString = upper ? hexStringUpper : hexStringLower; + size_t ret = cast(size_t) ctlz(c); + ret = (ret >> 2) + ((ret & 3) != 0); + size_t i = ret; + do + { + buf.ptr[--i] = hexStringUpper[c & 0xF]; + c >>= 4; + } + while(i); + return ret; +} + + size_t printHexAddress(ubyte c, ref char[2] buf, bool upper) { return printHexAddressGen!(ubyte, char)(c, buf, upper); } + size_t printHexAddress(ushort c, ref char[4] buf, bool upper) { return printHexAddressGen!(ushort, char)(c, buf, upper); } +pragma(inline, false) size_t printHexAddress(uint c, ref char[8] buf, bool upper) { return printHexAddressGen!(uint, char)(c, buf, upper); } +pragma(inline, false) size_t printHexAddress(ulong c, ref char[16] buf, bool upper) { return printHexAddressGen!(ulong, char)(c, buf, upper); } +static if (is(ucent)) +pragma(inline, false) size_t printHexAddress(ucent c, ref char[32] buf, bool upper) { return printHexAddressGen!(ucent, char)(c, buf, upper); } + + size_t printHexAddress(ubyte c, ref wchar[2] buf, bool upper) { return printHexAddressGen!(ubyte, wchar)(c, buf, upper); } + size_t printHexAddress(ushort c, ref wchar[4] buf, bool upper) { return printHexAddressGen!(ushort, wchar)(c, buf, upper); } +pragma(inline, false) size_t printHexAddress(uint c, ref wchar[8] buf, bool upper) { return printHexAddressGen!(uint, wchar)(c, buf, upper); } +pragma(inline, false) size_t printHexAddress(ulong c, ref wchar[16] buf, bool upper) { return printHexAddressGen!(ulong, wchar)(c, buf, upper); } +static if (is(ucent)) +pragma(inline, false) size_t printHexAddress(ucent c, ref wchar[32] buf, bool upper) { return printHexAddressGen!(ucent, wchar)(c, buf, upper); } + + size_t printHexAddress(ubyte c, ref dchar[2] buf, bool upper) { return printHexAddressGen!(ubyte, dchar)(c, buf, upper); } + size_t printHexAddress(ushort c, ref dchar[4] buf, bool upper) { return printHexAddressGen!(ushort, dchar)(c, buf, upper); } +pragma(inline, false) size_t printHexAddress(uint c, ref dchar[8] buf, bool upper) { return printHexAddressGen!(uint, dchar)(c, buf, upper); } +pragma(inline, false) size_t printHexAddress(ulong c, ref dchar[16] buf, bool upper) { return printHexAddressGen!(ulong, dchar)(c, buf, upper); } +static if (is(ucent)) +pragma(inline, false) size_t printHexAddress(ucent c, ref dchar[32] buf, bool upper) { return printHexAddressGen!(ucent, dchar)(c, buf, upper); } + +size_t printHexAddressGen(T, C)(T c, ref C[T.sizeof * 2] buf, bool upper) +{ + static if (T.sizeof == 16) + { + printHexAddress(cast(ulong)(c >> 64), buf[0 .. 16], upper); + printHexAddress(cast(ulong) c, buf[16 .. 32], upper); + } + else + { + immutable hexString = upper ? hexStringUpper : hexStringLower; + foreach_reverse(ref e; buf) + { + e = hexStringUpper[c & 0xF]; + c >>= 4; + } + } + return buf.length; +} + +static immutable hexStringUpper = "0123456789ABCDEF"; +static immutable hexStringLower = "0123456789abcdef"; + +pragma(inline, false) size_t printBufferShift(size_t length, size_t shift, scope char* ptr) { return printBufferShiftGen!char(length, shift, ptr); } +pragma(inline, false) size_t printBufferShift(size_t length, size_t shift, scope wchar* ptr) { return printBufferShiftGen!wchar(length, shift, ptr); } +pragma(inline, false) size_t printBufferShift(size_t length, size_t shift, scope dchar* ptr) { return printBufferShiftGen!dchar(length, shift, ptr); } + +size_t printBufferShiftGen(C)(size_t length, size_t shift, scope C* ptr) @trusted +{ + size_t i; + do ptr[i] = ptr[shift + i]; + while(++i < length); + return length; +} + +pragma(inline, false) size_t printSigned(int c, scope ref char[11] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } +pragma(inline, false) size_t printSigned(long c, scope ref char[21] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } +static if (is(cent)) +pragma(inline, false) size_t printSigned(cent c, scope ref char[40] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } + +pragma(inline, false) size_t printSigned(int c, scope ref wchar[11] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } +pragma(inline, false) size_t printSigned(long c, scope ref wchar[21] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } +static if (is(cent)) +pragma(inline, false) size_t printSigned(cent c, scope ref wchar[40] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } + +pragma(inline, false) size_t printSigned(int c, scope ref dchar[11] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } +pragma(inline, false) size_t printSigned(long c, scope ref dchar[21] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } +static if (is(cent)) +pragma(inline, false) size_t printSigned(cent c, scope ref dchar[40] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } + + +pragma(inline, false) size_t printSignedToTail(int c, scope ref char[11] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } +pragma(inline, false) size_t printSignedToTail(long c, scope ref char[21] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } +static if (is(cent)) +pragma(inline, false) size_t printSignedToTail(cent c, scope ref char[40] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } + +pragma(inline, false) size_t printSignedToTail(int c, scope ref wchar[11] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +pragma(inline, false) size_t printSignedToTail(long c, scope ref wchar[21] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +static if (is(cent)) +pragma(inline, false) size_t printSignedToTail(cent c, scope ref wchar[40] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } + +pragma(inline, false) size_t printSignedToTail(int c, scope ref dchar[11] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +pragma(inline, false) size_t printSignedToTail(long c, scope ref dchar[21] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +static if (is(cent)) +pragma(inline, false) size_t printSignedToTail(cent c, scope ref dchar[40] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } + +size_t printSignedGen(T, C, size_t N)(T c, scope ref C[N] buf, C sign) @trusted +{ + auto ret = printSignedToTail(c, buf, sign); + if (auto shift = buf.length - ret) + { + return printBufferShift(ret, shift, buf[].ptr); + } + return ret; +} + +size_t printSignedToTailGen(T, C, size_t N)(T c, scope ref C[N] buf, C sign) +{ + if (c < 0) + { + sign = '-'; + c = -c; + } + + auto ret = printUnsignedToTail(c, buf[1 .. N]); + + if (sign != '\0') + { + buf[$ - ++ret] = sign; + } + return ret; +} + +pragma(inline, false) size_t printUnsigned(uint c, scope ref char[10] buf) { return printUnsignedGen(c, buf); } +pragma(inline, false) size_t printUnsigned(ulong c, scope ref char[20] buf) { return printUnsignedGen(c, buf); } +static if (is(ucent)) +pragma(inline, false) size_t printUnsigned(ucent c, scope ref char[39] buf) { return printUnsignedGen(c, buf); } + +pragma(inline, false) size_t printUnsigned(uint c, scope ref wchar[10] buf) { return printUnsignedGen(c, buf); } +pragma(inline, false) size_t printUnsigned(ulong c, scope ref wchar[20] buf) { return printUnsignedGen(c, buf); } +static if (is(ucent)) +pragma(inline, false) size_t printUnsigned(ucent c, scope ref wchar[39] buf) { return printUnsignedGen(c, buf); } + +pragma(inline, false) size_t printUnsigned(uint c, scope ref dchar[10] buf) { return printUnsignedGen(c, buf); } +pragma(inline, false) size_t printUnsigned(ulong c, scope ref dchar[20] buf) { return printUnsignedGen(c, buf); } +static if (is(ucent)) +pragma(inline, false) size_t printUnsigned(ucent c, scope ref dchar[39] buf) { return printUnsignedGen(c, buf); } + +pragma(inline, false) size_t printUnsignedToTail(uint c, scope ref char[10] buf) { return printUnsignedToTailGen(c, buf); } +pragma(inline, false) size_t printUnsignedToTail(ulong c, scope ref char[20] buf) { return printUnsignedToTailGen(c, buf); } +static if (is(ucent)) +pragma(inline, false) size_t printUnsignedToTail(ucent c, scope ref char[39] buf) { return printUnsignedToTailGen(c, buf); } + +pragma(inline, false) size_t printUnsignedToTail(uint c, scope ref wchar[10] buf) { return printUnsignedToTailGen(c, buf); } +pragma(inline, false) size_t printUnsignedToTail(ulong c, scope ref wchar[20] buf) { return printUnsignedToTailGen(c, buf); } +static if (is(ucent)) +pragma(inline, false) size_t printUnsignedToTail(ucent c, scope ref wchar[39] buf) { return printUnsignedToTailGen(c, buf); } + +pragma(inline, false) size_t printUnsignedToTail(uint c, scope ref dchar[10] buf) { return printUnsignedToTailGen(c, buf); } +pragma(inline, false) size_t printUnsignedToTail(ulong c, scope ref dchar[20] buf) { return printUnsignedToTailGen(c, buf); } +static if (is(ucent)) +pragma(inline, false) size_t printUnsignedToTail(ucent c, scope ref dchar[39] buf) { return printUnsignedToTailGen(c, buf); } + +size_t printUnsignedToTailGen(T, C, size_t N)(T c, scope ref C[N] buf) @trusted +{ + static if (T.sizeof == 4) + { + if (c < 10) + { + buf[$ - 1] = cast(char)('0' + c); + return 1; + } + static assert(N == 10); + } + else + static if (T.sizeof == 8) + { + if (c <= uint.max) + { + return printUnsignedToTail(cast(uint)c, buf[$ - 10 .. $]); + } + static assert(N == 20); + } + else + static if (T.sizeof == 16) + { + if (c <= ulong.max) + { + return printUnsignedToTail(cast(ulong)c, buf[$ - 20 .. $]); + } + static assert(N == 39); + } + else + static assert(0); + size_t refLen = buf.length; + do { + T nc = c / 10; + buf[].ptr[--refLen] = cast(C)('0' + c - nc * 10); + c = nc; + } + while(c); + return buf.length - refLen; +} + +size_t printUnsignedGen(T, C, size_t N)(T c, scope ref C[N] buf) @trusted +{ + auto ret = printUnsignedToTail(c, buf); + if (auto shift = buf.length - ret) + { + return printBufferShift(ret, shift, buf[].ptr); + } + return ret; +} + +nothrow @trusted +pragma(inline, false) size_t extendASCII(char* from, wchar* to, size_t n) +{ + foreach (i; 0 .. n) + to[i] = from[i]; + return n; +} + +nothrow @trusted +pragma(inline, false) size_t extendASCII(char* from, dchar* to, size_t n) +{ + foreach (i; 0 .. n) + to[i] = from[i]; + return n; +} + +version (mir_test) unittest +{ + import mir.appender; + import mir.format; + + assert (stringBuf() << 123L << getData == "123"); + static assert (stringBuf() << 123 << getData == "123"); +} + +ref W printIntegralZeroImpl(C, size_t N, W, I)(scope return ref W w, I c, size_t zeroLen) +{ + static if (__traits(isUnsigned, I)) + alias impl = printUnsignedToTail; + else + alias impl = printSignedToTail; + C[N] buf = void; + size_t n = impl(c, buf); + static if (!__traits(isUnsigned, I)) + { + if (c < 0) + { + n--; + w.put(C('-')); + } + } + sizediff_t zeros = zeroLen - n; + if (zeros > 0) + { + do w.put(C('0')); + while(--zeros); + } + w.put(buf[$ - n .. $]); + return w; +} diff --git a/source/mir/ndslice/slice.d b/source/mir/ndslice/slice.d index 1441d7f8..8da691ec 100644 --- a/source/mir/ndslice/slice.d +++ b/source/mir/ndslice/slice.d @@ -112,6 +112,16 @@ auto toSlice(T)(T[] val) /// ditto auto toSlice(T)(T val) + if (hasAsSlice!T || __traits(hasMember, T, "moveToSlice")) +{ + static if (__traits(hasMember, T, "moveToSlice")) + return val.moveToSlice; + else + return val.asSlice; +} + +/// ditto +auto toSlice(T)(ref T val) if (hasAsSlice!T) { return val.asSlice; diff --git a/source/mir/parse.d b/source/mir/parse.d new file mode 100644 index 00000000..f970ceda --- /dev/null +++ b/source/mir/parse.d @@ -0,0 +1,266 @@ +/++ +$(H1 @nogc Parsing Utilities) + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: Ilya Yaroshenko ++/ +module mir.parse; + +import mir.primitives; +import std.range.primitives: isInputRange; + +/++ +Throws: nogc Exception in case of parse error or non-empty remaining input. ++/ +T fromString(T, Range)(scope auto ref Range r) +{ + static immutable excne = new Exception("fromString: remaining input is not empty after parsing " ~ T.stringof); + static immutable excfp = new Exception("fromString failed to parse " ~ T.stringof); + + T value; + if (parse!T(r, value)) + { + if (r.empty) + { + return value; + } + throw excne; + } + else + { + throw excfp; + } +} + +/// +@safe pure @nogc version (mir_test) unittest +{ + assert("123".fromString!int == 123); + + auto s = "123"; + assert(s.fromString!int == 123); + assert(s == ""); + + s = "123"; + assert(s[].fromString!int == 123); + assert(s == "123"); +} + +/++ ++/ +bool parse(T : byte, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && !__traits(isUnsigned, T)) +{ + int lvalue; + auto ret = parse!(int, Range)(r, lvalue); + value = cast(byte) lvalue; + return ret && value == lvalue; +} +/// ditto +bool parse(T : short, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && !__traits(isUnsigned, T)) +{ + int lvalue; + auto ret = parse!(int, Range)(r, lvalue); + value = cast(short) lvalue; + return ret && value == lvalue; +} + +/// ditto +pragma(inline, false) +bool parse(T : int, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && !__traits(isUnsigned, T)) +{ + return parseSignedImpl!(int, Range)(r, value); +} + +/// ditto +pragma(inline, false) +bool parse(T : long, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && !__traits(isUnsigned, T)) +{ + return parseSignedImpl!(long, Range)(r, value); +} + +/// ditto +bool parse(T : ubyte, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && __traits(isUnsigned, T)) +{ + uint lvalue; + auto ret = parse!(uint, Range)(r, lvalue); + value = cast(ubyte) lvalue; + return ret && value == lvalue; +} + +/// ditto +bool parse(T : ushort, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && __traits(isUnsigned, T)) +{ + uint lvalue; + auto ret = parse!(uint, Range)(r, lvalue); + value = cast(ushort) lvalue; + return ret && value == lvalue; +} + +/// ditto +pragma(inline, false) +bool parse(T : uint, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && __traits(isUnsigned, T)) +{ + return parseUnsignedImpl!(uint, Range)(r, value); +} + +/// ditto +pragma(inline, false) +bool parse(T : ulong, Range)(scope ref Range r, scope ref T value) + if (isInputRange!Range && __traits(isUnsigned, T)) +{ + return parseUnsignedImpl!(ulong, Range)(r, value); +} + + +/// +version (mir_test) unittest +{ + import std.meta: AliasSeq; + foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + auto str = "123"; + T val; + assert(parse(str, val)); + assert(val == 123); + str = "0"; + assert(parse(str, val)); + assert(val == 0); + str = "9"; + assert(parse(str, val)); + assert(val == 9); + str = ""; + assert(!parse(str, val)); + assert(val == 0); + str = "text"; + assert(!parse(str, val)); + assert(val == 0); + } +} + +/// +version (mir_test) unittest +{ + import std.meta: AliasSeq; + foreach (T; AliasSeq!(byte, short, int, long)) + { + auto str = "-123"; + T val; + assert(parse(str, val)); + assert(val == -123); + str = "-0"; + assert(parse(str, val)); + assert(val == 0); + str = "-9text"; + assert(parse(str, val)); + assert(val == -9); + assert(str == "text"); + enum m = T.min + 0; + str = m.stringof; + assert(parse(str, val)); + assert(val == T.min); + } +} + +/// +version (mir_test) unittest +{ + import std.meta: AliasSeq; + foreach (T; AliasSeq!(byte, short, int, long)) + { + } +} + +alias r1 = parseUnsignedImpl!(uint, string); +alias r2 = parseUnsignedImpl!(ulong, string); +alias r3 = parseSignedImpl!(int, string); +alias r4 = parseSignedImpl!(long, string); + +private bool parseUnsignedImpl(T, Range)(scope ref Range r, scope ref T value) + if(__traits(isUnsigned, T)) +{ + import core.checkedint: addu, mulu; + + bool sign; +B: + if (!r.empty) + { + auto f = r.front + 0u; + if (!sign && f == '+') + { + r.popFront; + sign = true; + goto B; + } + uint c = f - '0'; + if (c >= 10) + goto F; + T x = c; + for(;;) + { + r.popFront; + if (r.empty) + break; + c = r.front - '0'; + if (c >= 10) + break; + bool overflow; + T y = mulu(x, cast(uint)10, overflow); + if (overflow) + goto R; + x = y; + T z = addu(x, cast(uint)c, overflow); + if (overflow) + goto R; + x = z; + } + value = x; + return true; + } +F: value = 0; +R: return false; +} + +private bool parseSignedImpl(T, Range)(scope ref Range r, scope ref T value) + if(!__traits(isUnsigned, T)) +{ + import core.checkedint: negs; + import std.traits: Unsigned; + + bool sign; +B: + if (!r.empty) + { + auto f = r.front + 0u; + if (!sign && f == '-') + { + r.popFront; + sign = true; + goto B; + } + auto retu = (()@trusted=>parse(r, *cast(Unsigned!T*) &value))(); + // auto retu = false; + if (!retu) + goto R; + if (!sign) + { + if (value < 0) + goto R; + } + else + { + if (value < 0 && value != T.min) + goto R; + value = -value; + } + return true; + } +F: value = 0; +R: return false; +} diff --git a/source/mir/rc/array.d b/source/mir/rc/array.d index 8d15d6f4..94f2e7c1 100644 --- a/source/mir/rc/array.d +++ b/source/mir/rc/array.d @@ -116,6 +116,31 @@ struct mir_rcarray(T) return mir_slice!It([length], It(this)); } + /// + auto asSlice() const @property + { + import mir.ndslice.slice: mir_slice; + alias It = mir_rci!(const T); + return mir_slice!It([length], It(this.lightConst)); + } + + /// + auto asSlice() immutable @property + { + import mir.ndslice.slice: mir_slice; + alias It = mir_rci!(immutable T); + return mir_slice!It([length], It(this.lightImmutable)); + } + + /// + auto moveToSlice() @property + { + import core.lifetime: move; + import mir.ndslice.slice: mir_slice; + alias It = mir_rci!T; + return mir_slice!It([length], It(move(this))); + } + /++ Params: length = array length @@ -190,25 +215,6 @@ unittest static assert(is(typeof(fs) == Slice!(double*))); } -/// -version(mir_test) -@safe pure @nogc nothrow -unittest -{ - RCArray!double a = rcarray!double(1.0, 2, 5, 3); - assert(a[0] == 1); - assert(a[$ - 1] == 3); - - auto s = rcarray!char("hello!"); - assert(s[0] == 'h'); - assert(s[$ - 1] == '!'); - - alias rcstring = rcarray!(immutable char); - auto r = rcstring("string"); - assert(r[0] == 's'); - assert(r[$ - 1] == 'g'); -} - private template LikeArray(Range) { static if (__traits(identifier, Range) == "mir_slice") @@ -224,14 +230,14 @@ private template LikeArray(Range) /// auto rcarray(T = void, Range)(ref Range range) - if (is(T == void) && hasLength!Range && !is(Range == LightScopeOf!Range)) + if (is(T == void) && !is(Range == LightScopeOf!Range)) { return .rcarray(range.lightScope); } /// ditto auto rcarray(T = void, Range)(Range range) - if (is(T == void) && hasLength!Range && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) + if (is(T == void) && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) { static if (LikeArray!Range) { @@ -271,33 +277,63 @@ RCArray!V rcarray(T = void, V)(scope V[] values, bool deallocate) return .rcarray!V(values, deallocate); } -/++ -+/ +/// ditto template rcarray(T) if(!is(T == E[], E) && !is(T == void)) { /// auto rcarray(Range)(ref Range range) - if (hasLength!Range && !is(Range == LightScopeOf!Range)) + if (!is(Range == LightScopeOf!Range)) { return .rcarray!T(range.lightScope); } /// ditto auto rcarray(Range)(Range range) - if (hasLength!Range && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) + if (isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) { + import std.range.primitives: isInputRange; static if (LikeArray!Range) { return .rcarray!T(range.field); } - else + else static if (hasLength!Range) { - auto ret = RCArray!T(range.length, false); import mir.conv: emplaceRef; + auto ret = RCArray!T(range.length, false); size_t i; - foreach(ref e; range) - ret[i++].emplaceRef!T(e); + static if (isInputRange!Range) + for (; !range.empty; range.popFront) + ret[i++].emplaceRef!T(range.front); + else + static if (isPointer!Range) + foreach (e; *range) + ret[i++].emplaceRef!T(e); + else + foreach (e; range) + ret[i++].emplaceRef!T(e); + return ret; + } + else + { + import mir.appender: ScopedBuffer; + import mir.conv: emplaceRef; + ScopedBuffer!T a; + static if (isInputRange!Range) + for (; !range.empty; range.popFront) + a.put(range.front); + else + static if (isPointer!Range) + foreach (e; *range) + a.put(e); + else + foreach (e; range) + a.put(e); + scope values = a.data; + auto ret = RCArray!T(values.length, false); + ()@trusted { + a.moveDataAndEmplaceTo(ret[]); + }(); return ret; } } @@ -361,6 +397,37 @@ template rcarray(T) } } +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + RCArray!double a = rcarray!double(1.0, 2, 5, 3); + assert(a[0] == 1); + assert(a[$ - 1] == 3); + + auto s = rcarray!char("hello!"); + assert(s[0] == 'h'); + assert(s[$ - 1] == '!'); + + alias rcstring = rcarray!(immutable char); + auto r = rcstring("string"); + assert(r[0] == 's'); + assert(r[$ - 1] == 'g'); +} + +/// With Input Ranges +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import std.algorithm.iteration: filter; + static immutable numbers = [3, 2, 5, 2, 3, 7, 3]; + static immutable filtered = [5.0, 7]; + auto result = numbers.filter!(a => a > 3).rcarray!(immutable double); + static assert(is(typeof(result) == RCArray!(immutable double))); + assert (result[] == filtered); +} /++ Params: @@ -557,16 +624,16 @@ alias RCI = mir_rci; version(mir_test) @safe @nogc unittest { + import mir.ndslice.traits: isIterator; import mir.ndslice.slice; import mir.rc.array; - auto array = mir_rcarray!double(10); - auto slice = array.asSlice; + auto slice = mir_rcarray!double(10).asSlice; static assert(isIterator!(RCI!double)); static assert(is(typeof(slice) == Slice!(RCI!double))); auto matrix = slice.sliced(2, 5); static assert(is(typeof(matrix) == Slice!(RCI!double, 2))); - array[7] = 44; + slice[7] = 44; assert(matrix[1, 2] == 44); } diff --git a/source/mir/rc/context.d b/source/mir/rc/context.d index 29b3c621..24a97b30 100644 --- a/source/mir/rc/context.d +++ b/source/mir/rc/context.d @@ -190,6 +190,10 @@ package mixin template CommonRCImpl() ThisTemplate!(immutable T) lightImmutable()() scope return immutable @nogc nothrow @trusted @property { return *cast(typeof(return)*) &this; } + /// + ThisTemplate!(const Unqual!T) moveToConst()() scope return @nogc nothrow @trusted @property + { return move(*cast(typeof(return)*) &this); } + /// pragma(inline, true) size_t _counter() @trusted scope pure nothrow @nogc const @property diff --git a/source/mir/series.d b/source/mir/series.d index fd024824..3cad6242 100644 --- a/source/mir/series.d +++ b/source/mir/series.d @@ -139,7 +139,7 @@ See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). auto data = slice!double([index.length, 2], 0); // initialized to 0 value auto series = index.series(data); - series[0 .. $, 0][].opIndexAssign(series0); // fill first column + series[0 .. $, 0][] = series0; // fill first column series[0 .. $, 1][] = series1; // fill second column assert(data == [