From 6cf1d468b4445cd2ad85e2618859fcf34b7331fc Mon Sep 17 00:00:00 2001 From: 9il Date: Thu, 26 Dec 2019 12:10:44 +0700 Subject: [PATCH] add mir.variant and mir.rc --- doc/Makefile | 9 +- index.d | 2 + meson.build | 1 + source/mir/rc/array.d | 22 ++--- source/mir/rc/package.d | 145 ++++++++++++++++++++++++++- source/mir/rc/ptr.d | 45 +++++---- source/mir/rc/slim_ptr.d | 20 ++-- source/mir/variant.d | 206 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 404 insertions(+), 46 deletions(-) create mode 100644 source/mir/variant.d diff --git a/doc/Makefile b/doc/Makefile index 14ad6dd5..b06994cd 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -36,10 +36,11 @@ PACKAGE_mir = \ small_string \ small_array \ polynomial \ - format\ - parse\ - appender\ - exception\ + format \ + parse \ + appender \ + exception \ + variant \ PACKAGE_mir_algorithm = iteration setops PACKAGE_mir_array = allocation diff --git a/index.d b/index.d index ea7227c2..f71dc260 100644 --- a/index.d +++ b/index.d @@ -23,6 +23,7 @@ $(BOOKTABLE , $(TR $(TDNW $(MREF mir,math,constant)) $(TD Math constants)) $(TR $(TDNW $(MREF mir,polynomial)) $(TD Polynomial ref-counted structure)) $(LEADINGROW Reference counting) + $(TR $(TDNW $(MREF mir,rc)) $(TD Reference counting package and RC conversion utilities.)) $(TR $(TDNW $(MREF mir,rc,array)) $(TD Thread safe reference count array and the iterator to adopt it to ndslice.)) $(TR $(TDNW $(MREF mir,rc,ptr)) $(TD Thread safe reference count pointer with polymorphism support for strucs and objects.)) $(TR $(TDNW $(MREF mir,rc,slim_ptr)) $(TD Thread safe reference count pointer for strucs and objects.)) @@ -40,6 +41,7 @@ $(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,variant)) $(TD Variant Type (aka Algebraic Type) with clever member access)) $(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)) diff --git a/meson.build b/meson.build index 32ab1eda..e373fb7e 100644 --- a/meson.build +++ b/meson.build @@ -67,6 +67,7 @@ mir_algorithm_src = [ 'source/mir/small_array.d', 'source/mir/small_string.d', 'source/mir/type_info.d', + 'source/mir/variant.d', ] mir_algorithm_lib = library(meson.project_name(), diff --git a/source/mir/rc/array.d b/source/mir/rc/array.d index 97a7b26a..67bb4da1 100644 --- a/source/mir/rc/array.d +++ b/source/mir/rc/array.d @@ -9,12 +9,12 @@ import mir.rc.context; import mir.type_info; import std.traits; -private static immutable allocationExcMsg = "mir_rcarray: out of memory error."; +package static immutable allocationExcMsg = "mir_rcarray: out of memory error."; version (D_Exceptions) { import core.exception: OutOfMemoryError; - private static immutable allocationError = new OutOfMemoryError(allocationExcMsg); + package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); } /++ @@ -27,16 +27,16 @@ The implementation never adds roots into the GC. struct mir_rcarray(T) { /// - private T* _payload; - private ref inout(mir_rc_context) context() inout scope return pure nothrow @nogc @trusted @property + package T* _payload; + package ref inout(mir_rc_context) context() inout scope return pure nothrow @nogc @trusted @property { assert(_payload); return (cast(inout(mir_rc_context)*)_payload)[-1]; } - private void _reset() { _payload = null; } + package void _reset() { _payload = null; } - private alias ThisTemplate = .mir_rcarray; - private alias _thisPtr = _payload; + package alias ThisTemplate = .mir_rcarray; + package alias _thisPtr = _payload; /// void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe @@ -176,11 +176,11 @@ struct mir_rcarray(T) static if (isImplicitlyConvertible!(const T, T)) static if (isImplicitlyConvertible!(const Unqual!T, T)) - private alias V = const Unqual!T; + package alias V = const Unqual!T; else - private alias V = const T; + package alias V = const T; else - private alias V = T; + package alias V = T; } @@ -215,7 +215,7 @@ unittest static assert(is(typeof(fs) == Slice!(double*))); } -private template LikeArray(Range) +package template LikeArray(Range) { static if (__traits(identifier, Range) == "mir_slice") { diff --git a/source/mir/rc/package.d b/source/mir/rc/package.d index 18ac136d..d3bd061e 100644 --- a/source/mir/rc/package.d +++ b/source/mir/rc/package.d @@ -1,10 +1,153 @@ /++ $(H1 Thread-safe reference-counted arrays and pointers) -Publicly imports $(MREF mir,rc,array) and $(MREF mir,rc,ptr). +Mir provides two kinds of ref-counting pointers and two kinds of ref-counted arrays. + +The first kind pointer is `RCPtr`, which consists of a pointer to the context and pointer to the value.`RCPtr` supports structural and object polymorphism. It allows getting members with the same context as the root. +The second kind is `SlimRCPtr`, which consist only from a pointer to the value. The context for `SlimRCPtr`is computed using a fixed-length memory shift from the pointer to the value. +`SlimRCPtr` can be converted to an `RCPtr` and to an `RCArray` of the one element. + +`RCArray` is an array type without range primitives. It's length can't be reduced after construction.In the other hand, `Slice!(RCI!(T))` is an ndslice with all random-access range primitives.`RCI` is an iterator, which consists of `RCArray` and the pointer to the current element. +`RCArray!T` can be converted or moved to `Slice!(RCI!(T))` using `.asSlice` or `.moveToSlice` methods respectively. + +$(RED `RCArray!T` aliases itself to a common D array slice. This feature may cause a segmentation fault in safe code if used without DIP1000.) + +`RCPtr!T` can be constructed from an element index and `RCArray!T` / `Slice!(RCI!(T))`. + +The package publicly imports $(MREF mir,rc,array), $(MREF mir,rc,ptr), and $(MREF mir,rc,slim_ptr). + +See_also: $(MREF mir,ndslice). +/ module mir.rc; public import mir.rc.array; public import mir.rc.ptr; public import mir.rc.slim_ptr; + +import mir.ndslice.slice; + +/++ +Returns: shared pointer constructed from the slim shared pointer. + +The function has zero computation cost. ++/ +RCPtr!F toRCPtr(F)(return SlimRCPtr!F contextAndValue) @trusted +{ + typeof(return) ret; + ret._value = contextAndValue._value; + ret._context = &contextAndValue.context(); + contextAndValue._value = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import core.lifetime: move; + struct S + { + double e; + } + struct C + { + int i; + S s; + } + + auto a = createSlimRC!C(10, S(3)); + auto s = a.move.toRCPtr.shareMember!"s"; + assert(s._counter == 1); + assert(s.e == 3); +} + +/++ +Returns: shared pointer constructed with the `array`'s context and the value points to `array[index]`. + +The function has zero computation cost. ++/ +RCPtr!F toRCPtrAt(F)(return RCArray!F array, size_t index) @trusted + if (!is(R == class) && !is(R == interface)) +in { + assert(index < array.length, "toRCPtrAt: index should be less then array.length"); +} +body { + typeof(return) ret; + ret._value = array._payload + index; + ret._context = &array.context(); + array._payload = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + struct S { double e; } + + auto a = RCArray!S(10); + a[3].e = 4; + + auto s = a.toRCPtrAt(3); + + assert(s._counter == 2); + assert(s.e == 4); +} + +/// ditto +RCPtr!F toRCPtrAt(F)(return Slice!(RCI!F) array, size_t index) @trusted + if (!is(R == class) && !is(R == interface)) +in { + assert(index < array.length, "toRCPtrAt: index should be less then array.length"); +} +body { + typeof(return) ret; + ret._value = array._iterator._iterator + index; + ret._context = &array._iterator._array.context(); + array._iterator._array._payload = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + struct S { double e; } + + auto a = RCArray!S(10).asSlice[5 .. $]; + a[3].e = 4; + + auto s = a.toRCPtrAt(3); + + assert(s._counter == 2); + assert(s.e == 4); +} + +/++ +Returns: RC array length of one constructed from the slim shared pointer. + +The function has zero computation cost. ++/ +RCArray!F toRCArray(F)(return SlimRCPtr!F context) @trusted +{ + typeof(return) ret; + ret._payload = context._value; + context._value = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + struct S { double e; } + + auto a = createSlimRC!S(4).toRCArray; + assert(a._counter == 1); + assert(a.length == 1); + assert(a[0].e == 4); +} diff --git a/source/mir/rc/ptr.d b/source/mir/rc/ptr.d index fdcf2627..893bc4e4 100644 --- a/source/mir/rc/ptr.d +++ b/source/mir/rc/ptr.d @@ -9,13 +9,13 @@ import mir.rc.context; import mir.type_info; import std.traits; -private static immutable allocationExcMsg = "mir_rcptr: out of memory error."; -private static immutable getExcMsg = "mir_rcptr: trying to use null value."; +package static immutable allocationExcMsg = "mir_rcptr: out of memory error."; +package static immutable getExcMsg = "mir_rcptr: trying to use null value."; version (D_Exceptions) { import core.exception: OutOfMemoryError, InvalidMemoryOperationError; - private static immutable allocationError = new OutOfMemoryError(allocationExcMsg); + package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); } /++ @@ -34,17 +34,17 @@ struct mir_rcptr(T) /// static if (is(T == class) || is(T == interface)) - private Unqual!T _value; + package Unqual!T _value; else - private T* _value; - private mir_rc_context* _context; + package T* _value; + package mir_rc_context* _context; - private ref inout(mir_rc_context) context() inout scope return @trusted @property + package ref inout(mir_rc_context) context() inout scope return @trusted @property { return *_context; } - private void _reset() + package void _reset() { _value = null; _context = null; @@ -55,7 +55,7 @@ struct mir_rcptr(T) return cast(inout(void)*) _value; } - private alias ThisTemplate = .mir_rcptr; + package alias ThisTemplate = .mir_rcptr; /// ditto alias opUnary(string op : "*") = _get_value; @@ -122,7 +122,7 @@ struct mir_rcptr(T) static if (!is(T == interface) && !__traits(isAbstractClass, T)) { - private this(Args...)(auto ref Args args) + package this(Args...)(auto ref Args args) { () @trusted { _context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); @@ -146,28 +146,30 @@ struct mir_rcptr(T) alias RCPtr = mir_rcptr; /++ +Returns: shared pointer of the member and the context from the current pointer. +/ auto shareMember(string member, T, Args...)(return mir_rcptr!T context, auto ref Args args) { + import core.lifetime: move; void foo(A)(auto ref A) {} static if (args.length) { // breaks safaty if (false) foo(__traits(getMember, context._get_value, member)(forward!args)); - return (()@trusted => createRCWithContext(context, __traits(getMember, context._get_value, member)(forward!args)))(); + return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member)(forward!args), context.move))(); } else { // breaks safaty if (false) foo(__traits(getMember, context._get_value, member)); - return (()@trusted => createRCWithContext(context, __traits(getMember, context._get_value, member)))(); + return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member), context.move))(); } } /++ Returns: shared pointer constructed with current context. +/ -@system .mir_rcptr!R createRCWithContext(R, F)(return const mir_rcptr!F context, return R value) +@system .mir_rcptr!R createRCWithContext(R, F)(return R value, return const mir_rcptr!F context) if (is(R == class) || is(R == interface)) { typeof(return) ret; @@ -179,7 +181,7 @@ Returns: shared pointer constructed with current context. } ///ditto -@system .mir_rcptr!R createRCWithContext(R, F)(return const mir_rcptr!F context, return ref R value) +@system .mir_rcptr!R createRCWithContext(R, F)(return ref R value, return const mir_rcptr!F context) if (!is(R == class) && !is(R == interface)) { typeof(return) ret; @@ -197,21 +199,24 @@ Provides polymorphism abilities for classes and structures with `alias this` syn mir_rcptr!R castTo(R, T)(return mir_rcptr!T context) @trusted if (isImplicitlyConvertible!(T, R)) { - return createRCWithContext(context, cast(R)context._get_value); + import core.lifetime: move; + return createRCWithContext(cast(R)context._get_value, move(context)); } /// ditto -mir_rcptr!(const R) castTo(R, T)(return const mir_rcptr!T context) @trusted const +mir_rcptr!(const R) castTo(R, T)(return const mir_rcptr!T context) @trusted if (isImplicitlyConvertible!(const T, const R)) { - return createRCWithContext(*cast(mir_rcptr!T*)&context, cast(const R)context._get_value); + import core.lifetime: move; + return createRCWithContext(cast(const R)context._get_value, move(*cast(mir_rcptr!T*)&context)); } /// ditto -mir_rcptr!(immutable R) castTo(R, T)(return immutable mir_rcptr!T context) @trusted immutable +mir_rcptr!(immutable R) castTo(R, T)(return immutable mir_rcptr!T context) @trusted if (isImplicitlyConvertible!(immutable T, immutable R)) { - return createRCWithContext(*cast(mir_rcptr!T*)&context, cast(immutable R)context._get_value); + import core.lifetime: move; + return createRCWithContext(cast(immutable R)context._get_value, move(*cast(mir_rcptr!T*)&context)); } @@ -302,7 +307,7 @@ unittest version(unittest): -private struct _test_unpure_system_dest_s__ { +package struct _test_unpure_system_dest_s__ { static int numStructs; int i; diff --git a/source/mir/rc/slim_ptr.d b/source/mir/rc/slim_ptr.d index beeb6d1a..4f585c3b 100644 --- a/source/mir/rc/slim_ptr.d +++ b/source/mir/rc/slim_ptr.d @@ -9,13 +9,13 @@ import mir.rc.context; import mir.type_info; import std.traits; -private static immutable allocationExcMsg = "mir_slim_rcptr: out of memory error."; -private static immutable getExcMsg = "mir_slim_rcptr: trying to use null value."; +package static immutable allocationExcMsg = "mir_slim_rcptr: out of memory error."; +package static immutable getExcMsg = "mir_slim_rcptr: trying to use null value."; version (D_Exceptions) { import core.exception: OutOfMemoryError, InvalidMemoryOperationError; - private static immutable allocationError = new OutOfMemoryError(allocationExcMsg); + package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); } /++ @@ -34,17 +34,17 @@ struct mir_slim_rcptr(T) /// static if (is(T == class) || is(T == interface)) - private Unqual!T _value; + package Unqual!T _value; else - private T* _value; + package T* _value; - private ref inout(mir_rc_context) context() inout scope return pure nothrow @nogc @trusted @property + package ref inout(mir_rc_context) context() inout scope return pure nothrow @nogc @trusted @property { assert(_value); return (cast(inout(mir_rc_context)*)_value)[-1]; } - private void _reset() + package void _reset() { _value = null; } @@ -54,7 +54,7 @@ struct mir_slim_rcptr(T) return cast(inout(void)*) _value; } - private alias ThisTemplate = .mir_slim_rcptr; + package alias ThisTemplate = .mir_slim_rcptr; /// ditto alias opUnary(string op : "*") = _get_value; @@ -118,7 +118,7 @@ struct mir_slim_rcptr(T) static if (!is(T == interface) && !__traits(isAbstractClass, T)) { - private this(Args...)(auto ref Args args) + package this(Args...)(auto ref Args args) { () @trusted { auto context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); @@ -212,7 +212,7 @@ unittest version(unittest): -private struct _test_unpure_system_dest_s__ { +package struct _test_unpure_system_dest_s__ { static int numStructs; int i; diff --git a/source/mir/variant.d b/source/mir/variant.d new file mode 100644 index 00000000..d6c7d3e3 --- /dev/null +++ b/source/mir/variant.d @@ -0,0 +1,206 @@ +/++ +This module implements a generic variant type. ++/ +module mir.variant; + +private static immutable variantExceptionMsg = "mir.variant: the variant stores other type then requested."; +private static immutable variantNulllExceptionMsg = "mir.variant: the variant is empty and doesn't store any value."; +private static immutable variantMemberExceptionMsg = "mir.variant: the variant is stores the type that isn't compatible with the user proveded visitor and arguments."; + +version (D_Exceptions) +{ + private static immutable variantException = new Exception(variantExceptionMsg); + private static immutable variantNulllException = new Exception(variantNulllExceptionMsg); + private static immutable variantMemberException = new Exception(variantMemberExceptionMsg); +} + +/++ +Variant Type (aka Algebraic Type) with clever member access. + +Compatible with BetterC mode. ++/ +struct Variant(Types...) + if (Types.length) +{ + import core.lifetime: move; + import mir.conv: emplaceRef; + import std.meta: anySatisfy; + import std.traits: Largest, hasElaborateDestructor, hasElaborateAssign; + + private alias _Types = Types; + + private void[Largest!Types.sizeof] payload = void; + private uint type; // 0 for unininit value, index = type - 1 + private enum hasDestructor = anySatisfy!(hasElaborateDestructor, Types); +pure nothrow @nogc: + static if (hasDestructor) + ~this() + { + S: switch (type) + { + static foreach (i, T; Types) static if (hasElaborateDestructor!T) + { + case i + 1: + .destroy!false(trustedGet!T); + break S; + } + default: break; + } + type = 0; + } + + static if (anySatisfy!(hasElaborateAssign, Types)) + this(this) + { + S: switch (type) + { + static foreach (i, T; Types) static if (hasElaborateAssign!T) + { + case i + 1: + __ctor(trustedGet!T); + break S; + } + default: break; + } + } + + /// + this(typeof(null)) + { + } + + /// + void opAssign(typeof(null)) + { + static if (hasDestructor) + __dtor; + type = 0; + } + + /// + void opAssign(Variant value) + { + static if (hasDestructor) + __dtor; + emplaceRef(this, move(value)); + } + + static foreach (i, T; Types) + /// + void opAssign(T value) + { + static if (hasDestructor) + __dtor; + type = i + 1; + emplaceRef(trustedGet!T, move(value)); + } + + static foreach (i, T; Types) + /// + this(T value) + { + type = i + 1; + emplaceRef(trustedGet!T, move(value)); + } + + static foreach (i, T; Types) + /// + ref inout(E) get(E : T)() @property return inout + { + import mir.utility: _expect; + if (_expect(i + 1 != type, false)) + { + if (i == 0) + { + version(D_Exceptions) + throw variantNulllException; + else + assert(0, variantNulllExceptionMsg); + } + version(D_Exceptions) + throw variantException; + else + assert(0, variantExceptionMsg); + } + return trustedGet!T; + } + + static foreach (i, T; Types) + /// Zero cost always nothrow `get` alternative + ref inout(E) trustedGet(E : T)() @trusted @property return inout nothrow + { + assert (i + 1 == type); + return *cast(inout(T)*)payload.ptr; + } + + /// + bool empty() const @property + { + return type == 0; + } +} + +/++ +Params: + visitor = a function name alias + forceAllTypes = if `true` checks at compile time, that the member can be called for all types. ++/ +template visit(alias visitor, bool forceAllTypes = true) +{ + import std.traits: Unqual; + /// + auto ref visit(V, Args)(auto ref V variant, auto ref Args args) + if (is(Unqual!V : Variant!Types, Types)) + { + import mir.functional: forward; + switch (variant.type) + { + case 0: + version(D_Exceptions) + throw variantNulllException; + else + assert(0, variantNulllExceptionMsg); + static foreach (i, T; V._Types) + static if (forceAllTypes || __traits(compiles, { return visitor(variant.trustedGet!T, forward!args); })) + { + case i + 1: + return visitor(variant.trustedGet!T, forward!args); + } + else + static if (forceAllTypes) + static assert(0, V.stringof ~ ": the visitor cann't be caled with type (first argument) " ~ T.stringof ~ " and additional arguments " ~ Args.stringof); + default: + version(D_Exceptions) + throw variantMemberException; + else + assert(0, variantMemberExceptionMsg); + } + } +} + +/++ +Params: + visitor = a function name alias ++/ +template optionalVisit(alias visitor) +{ + import std.traits: Unqual; + /// + bool optionalVisit(Result, V, Args)(out Result result, auto ref V variant, auto ref Args args) + if (is(Unqual!V : Variant!Types, Types)) + { + import mir.functional: forward; + switch (variant.type) + { + static foreach (i, T; V._Types) + static if (__traits(compiles, { result = visitor(variant.trustedGet!T, forward!args); })) + { + case i + 1: + result = visitor(variant.trustedGet!T, forward!args); + return true; + } + default: + return false; + } + } +}