diff --git a/.gitmodules b/.gitmodules index 99614c6b..47e2aec2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "etc/libdparse"] path = etc/libdparse url = https://github.com/dlang-community/libdparse.git -[submodule "etc/iz"] - path = etc/iz - url = https://gitlab.com/basile.b/iz.git [submodule "etc/stdx-allocator"] path = etc/stdx-allocator url = https://github.com/dlang-community/stdx-allocator.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f80d582..601f7650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,17 @@ -# v3.9.17-dev +# v3.9.18-dev ## Enhancements - Search and Replace: after a failed search give the editor the focus back. +## Bugs fixed + +- crash to desktop for certain invalid or incomplete D constructs. (#107) + +## Other + +- dexed-d does not rely on the old "iz" library + # v3.9.17 ## Enhancements diff --git a/dexed-d/dub.json b/dexed-d/dub.json index 1c28966e..754ec718 100644 --- a/dexed-d/dub.json +++ b/dexed-d/dub.json @@ -6,12 +6,9 @@ "dependencies" : { "libdparse" : { "path" : "../etc/libdparse" - }, - "iz" : { - "path" : "../etc/iz" } }, "dflags" : [ "-link-defaultlib-shared=false" ] -} +} \ No newline at end of file diff --git a/dexed-d/src/common.d b/dexed-d/src/common.d index 202f8907..58d2a5e0 100644 --- a/dexed-d/src/common.d +++ b/dexed-d/src/common.d @@ -7,7 +7,8 @@ import import dparse.lexer, dparse.ast, dparse.parser, dparse.rollback_allocator; import - iz.memory, iz.containers; + iz; + export extern(C) int d_rt_init(){ return rt_init(); } export extern(C) int d_rt_term(){ return rt_term(); } @@ -402,32 +403,6 @@ unittest assert(stream.data != ""); } -/** - * Produces and visits the AST for a source code. - * - * This function is used to handle the content of a MixinExpression in an - * ASTVisitor. - */ -T parseAndVisit(T : ASTVisitor, A...)(const(char)[] source, A a) -{ - import std.functional; - - RollbackAllocator allocator; - LexerConfig config = LexerConfig("", StringBehavior.source, WhitespaceBehavior.skip); - StringCache cache = StringCache(StringCache.defaultBucketCount); - const(Token)[] tokens = getTokensForParser(cast(ubyte[]) source, config, &cache); - Module mod = parseModule(tokens, "", &allocator, toDelegate(&ignoreErrors)); - T result = construct!(T)(a); - result.visit(mod); - return result; -} - -/** - * By default libdparse outputs errors and warnings to the standard streams. - * This function prevents that. - */ -void ignoreErrors(string, size_t, size_t, string, bool) @system {} - /** * Split a C string representing a list of filenames into an array of strings. * The filenames are separated with the system path separator. @@ -459,7 +434,7 @@ public: * the Pascal side, e.g on ref count decrement. */ static FpcArray!T* fromArray(A)(auto ref A array) nothrow @nogc - if (is(Unqual!A == T[]) || is(Unqual!A == iz.containers.Array!T)) + if (is(Unqual!A == T[]) || is(Unqual!A == iz.Array!T)) { alias RT = FpcArray!T; enum isChar = is(Unqual!T == char); diff --git a/dexed-d/src/ddoc_template.d b/dexed-d/src/ddoc_template.d index 6ede467e..b66c9a65 100644 --- a/dexed-d/src/ddoc_template.d +++ b/dexed-d/src/ddoc_template.d @@ -5,9 +5,9 @@ import import std.array, std.conv, std.string; import - iz.memory, iz.sugar; + iz; import - dparse.ast, dparse.lexer, dparse.parser, dparse.rollback_allocator; + dparse.ast, dparse.lexer, dparse.rollback_allocator, parser; import common; @@ -67,7 +67,7 @@ public: const(char)* result() { return app.data.toStringz(); } - override void visit(const(ThrowStatement) ts) + override void visit(const(ThrowExpression) ts) { _throws = true; } diff --git a/dexed-d/src/halstead.d b/dexed-d/src/halstead.d index 12dd8ac6..01822395 100644 --- a/dexed-d/src/halstead.d +++ b/dexed-d/src/halstead.d @@ -7,10 +7,10 @@ import import std.stdio, std.ascii, std.digest.crc, std.range: iota; import - dparse.ast, dparse.lexer, dparse.parser, dparse.rollback_allocator; + dparse.ast, dparse.lexer, dparse.rollback_allocator, parser; +import + iz; import - iz.memory, iz.containers, iz.sugar; -version(unittest){} else import common; /** @@ -477,7 +477,7 @@ private final class HalsteadMetric: ASTVisitor st.accept(this); } - override void visit(const(ThrowStatement) st) + override void visit(const(ThrowExpression) st) { operators["throw"] +=1; st.accept(this); diff --git a/dexed-d/src/imports.d b/dexed-d/src/imports.d index ddd726cc..f1c34ba7 100644 --- a/dexed-d/src/imports.d +++ b/dexed-d/src/imports.d @@ -5,9 +5,9 @@ import import std.algorithm, std.array, std.file, std.functional; import - iz.memory; + iz; import - dparse.lexer, dparse.ast, dparse.parser, dparse.rollback_allocator; + dparse.lexer, dparse.ast, parser, dparse.rollback_allocator; import common; diff --git a/dexed-d/src/iz.d b/dexed-d/src/iz.d new file mode 100644 index 00000000..6b48eccd --- /dev/null +++ b/dexed-d/src/iz.d @@ -0,0 +1,2262 @@ +module iz; + +import + core.stdc.string, core.stdc.stdlib, core.exception, core.lifetime; +import + std.traits, std.meta, std.typecons, std.range; +import + iz; + +alias Ptr = void*; + +/** + * Detects wether T is an instantiated template. + */ +template isTemplateInstance(alias T : Base!Args, alias Base, Args...) +{ + enum isTemplateInstance = is(typeof(T)); +} + +/// ditto +template isTemplateInstance(T : Base!Args, alias Base, Args...) +{ + enum isTemplateInstance = is(T); +} + +/// ditto +template isTemplateInstance(T) +{ + enum isTemplateInstance = false; +} + +/// ditto +template isTemplateInstance(alias T) +{ + enum isTemplateInstance = isTemplateInstance!(typeof(T)); +} + +/** + * Like malloc() but for @safe context. + */ +Ptr getMem(size_t size) nothrow @trusted @nogc +in +{ + //assert(size); +} +do +{ + auto result = malloc(size); + if (!result) + throw construct!OutOfMemoryError; + return result; +} + +/** + * Like realloc() but for @safe context. + */ +Ptr reallocMem(ref Ptr src, size_t newSize) nothrow @trusted @nogc +in +{ + assert(newSize); +} +do +{ + src = realloc(src, newSize); + if (!src) + throw construct!OutOfMemoryError; + return src; +} + +/** + * Like memmove() but for @safe context. + * dst and src can overlap. + * + * Params: + * dst = The data source. + * src = The data destination. + * count = The count of byte to meove from src to dst. + * Returns: + * the pointer to the destination, (same as dst). + */ +Ptr moveMem(Ptr dst, const Ptr src, size_t count) nothrow @trusted @nogc +in +{ + if (count) + { + assert(dst); + assert(src); + } +} +do +{ + return memmove(dst, src, count); +} + +/** + * Like memmove() but for @safe context. + * dst and src can't overlap. + * + * Params: + * dst = The data source. + * src = The data destination. + * count = The count of byte to meove from src to dst. + * Returns: + * the pointer to the destination, (same as dst). + */ +Ptr copyMem(Ptr dst, Ptr src, size_t count) nothrow @trusted @nogc +in +{ + if (count) + { + assert(dst); + assert(src); + assert(dst + count <= src || dst >= src + count); + } +} +do +{ + auto result = memcpy(dst, src, count); + if (!result) + throw construct!OutOfMemoryError; + return result; +} + +/** + * Frees a manually allocated pointer to a basic type. + * Like free() but for @safe context. + * + * Params: + * src = The pointer to free. + */ +void freeMem(T)(auto ref T src) nothrow @trusted @nogc +if (isPointer!T && isBasicType!(PointerTarget!T)) +{ + if (src) + { + free(cast(void*)src); + src = null; + } +} + +/** + * This enum must be used as an UDA to mark a variable of a type that looks + * like GC-managed but that is actually not GC-managed. + */ +enum NoGc; + +/** + * When this enum is used as UDA on aggregate types whose instances are + * created with construct() a compile time message indicates if a GC range + * will be added for the members. + */ +enum TellRangeAdded; + +/** + * When this enum is used as UDA on aggregate types whose instances are + * created with construct() they won't be initialized, i.e the + * static layout representing the initial value of the members is not copied. + * + * For example it can be used on a struct that has a $(D @disable this()) and + * when the others constructor are suposed to do the initialization job. + */ +enum NoInit; +/// +unittest +{ + @NoInit static struct Foo{int a = 42;} + Foo* foo = construct!Foo; + // initializer well skipped + assert(foo.a != 42); + destruct(foo); +} + +/** + * Indicates if an aggregate contains members that might be + * collected by the garbage collector. This is used in $(D construct) + * to determine if the content of a manually allocated aggregate must + * be declared to the GC. + */ +template MustAddGcRange(T) +if (is(T==struct) || is(T==union) || is(T==class)) +{ + string check() + { + import std.meta: aliasSeqOf; + import std.range: iota; + + string managedMembers; + + enum addManaged = q{managedMembers ~= " " ~ T.tupleof[i].stringof;}; + + // TODO: allow CPP classes detection when protection compliance removed. + static if (is(T == class) /*&& (!isCppClass!T)*/) + { + foreach(BT; BaseClassesTuple!T) + { + string m = MustAddGcRange!BT; + if (m.length) + managedMembers ~= " " ~ m; + } + } + // TODO: use __trait(allMembers) when protection compliance removed. + // ".tupleof" doesn't include the static fields. + foreach(i; aliasSeqOf!(iota(0, T.tupleof.length))) + { + static if (!is(typeof(T.tupleof[i])== void)) + { + alias MT = typeof(T.tupleof[i]); + static if (isArray!MT && !hasUDA!(T.tupleof[i], NoGc) && hasManagedDimension!MT) + mixin(addManaged); + else static if (isPointer!MT && !hasUDA!(T.tupleof[i], NoGc)) + mixin(addManaged); + else static if (is(MT == class) && (!is(MT : T)) && !hasUDA!(T.tupleof[i], NoGc) + && !(isTemplateInstance!T /*&& staticIndexOf!(MT,TemplateArgsOf!T) > 0*/)) + { + // failure here when the class is a template and when one of the member + // type is one of the template argument. + //pragma(msg, T.stringof, " ", MT.stringof); + static if (MustAddGcRange!MT) + mixin(addManaged); + } + else static if (is(MT == struct) && !is(MT == T) && !hasUDA!(T.tupleof[i], NoGc)) + { + static if (MustAddGcRange!MT) + mixin(addManaged); + } + else static if (is(MT == union) && !is(MT == T) && !hasUDA!(T.tupleof[i], NoGc)) + { + static if (MustAddGcRange!MT) + mixin(addManaged); + } + } + } + return managedMembers; + } + + static if (hasUDA!(T, NoGc)) + static immutable MustAddGcRange = []; + else + static immutable MustAddGcRange = check(); + + static if (hasUDA!(T, TellRangeAdded)) + { + static if (MustAddGcRange.length) + pragma(msg, "a GC range will be added for any new " ~ T.stringof ~ + ", because of: " ~ MustAddGcRange); + else + pragma(msg, "a GC range wont be added for any new " ~ T.stringof); + } + +} +/// +unittest +{ + // 'a' will be managed with expand/Shrink + class Foo{@NoGc int[] a; @NoGc void* b;} + static assert(!MustAddGcRange!Foo); + // 'a' will be managed with '.length' so druntime. + class Bar{int[] a; @NoGc void* b;} + // b's annotation is canceled by a type. + static assert(MustAddGcRange!Bar); + // Baz base is not @NoGc + class Baz: Bar{@NoGc void* c;} + static assert(MustAddGcRange!Baz); +} + +package template hasManagedDimension(T) +{ + import std.range: ElementType; + static if (isDynamicArray!T) + enum hasManagedDimension = true; + else static if (isStaticArray!T) + enum hasManagedDimension = hasManagedDimension!(ElementType!T); + else + enum hasManagedDimension = false; +} + +unittest +{ + alias A0 = int[]; + static assert(hasManagedDimension!A0); + alias A1 = int[2][]; + static assert(hasManagedDimension!A1); + alias A2 = int[][2]; + static assert(hasManagedDimension!A2); + alias A3 = int[3][2]; + static assert(!hasManagedDimension!A3); + alias A4 = int[][3][2]; + static assert(hasManagedDimension!A4); +} + +unittest +{ + class Foo{int[][2][4] a;} + static assert(MustAddGcRange!Foo); + class Bar{@NoGc int[][2][4] a;} + static assert(!MustAddGcRange!Bar); +} + +/** + * When mixed in a aggregate this template has for effect to disable the usage + * the $(D new) operator. + */ +mixin template disableNew() +{ + @disable new(); +} +/// +unittest +{ + // class requiring users to use allocators. + class NotUsableWithNew + { + mixin disableNew; + } + + // statically verify that `new` cannot be used. + static assert(!__traits(compiles, new NotUsableWithNew)); + + // Ok with a custom allocator + auto a = construct!NotUsableWithNew(); + destruct(a); +} + +/** + * When mixed in class this template has for effect to automatically call the + * nearest inherited destructor if no destructor is present, otherwise a call + * to $(D callInheritedDtor()) should be made in the last LOC of the destructor. + */ +mixin template inheritedDtor() +{ + + static assert(is(typeof(this) == class)); + + private + { + import std.traits: BaseClassesTuple; + + alias __iz_B = BaseClassesTuple!(typeof(this)); + enum hasDtor = __traits(hasMember, typeof(this), "__dtor"); + static if (hasDtor && !__traits(isSame, __traits(parent, typeof(this).__dtor), typeof(this))) + enum inDtor = true; + else + enum inDtor = false; + } + + public void callInheritedDtor(classT = typeof(this))() + { + import std.meta: aliasSeqOf; + import std.range: iota; + + foreach(i; aliasSeqOf!(iota(0, __iz_B.length))) + static if (__traits(hasMember, __iz_B[i], "__xdtor")) + { + mixin("this." ~ __iz_B[i].stringof ~ ".__xdtor;"); + break; + } + } + + static if (!hasDtor || inDtor) + public ~this() {callInheritedDtor();} +} + +/** + * Returns a new, GC-free, class instance. + * + * Params: + * CT = A class type. + * a = Variadic parameters passed to the constructor. + */ +CT construct(CT, A...)(A a) @trusted +if (is(CT == class) && !isAbstractClass!CT) +{ + auto size = typeid(CT).initializer.length; + auto memory = getMem(size); + static if (!hasUDA!(CT, NoInit)) + memory[0 .. size] = typeid(CT).initializer[]; + static if (__traits(hasMember, CT, "__ctor")) + (cast(CT) (memory)).__ctor(a); + static if (MustAddGcRange!CT) + { + import core.memory: GC; + GC.addRange(memory, size, typeid(CT)); + } + return cast(CT) memory; +} + + +/** + * Returns a new, GC-free, class instance. + * + * This overload is designed to create factories and like the default + * Object.factory method, it only calls, if possible, the default constructor. + * The factory() function implemented in this iz.memory is based on this + * construct() overload. + * + * Params: + * tic = The TypeInfo_Class of the Object to create. + */ +Object construct(TypeInfo_Class tic) @trusted +{ + if (tic.m_flags & 64) + return null; + auto size = tic.initializer.length; + auto memory = getMem(size); + memory[0 .. size] = tic.initializer[]; + Object result = cast(Object) memory; + if (tic.defaultConstructor) + tic.defaultConstructor(result); + import core.memory: GC; + GC.addRange(memory, size, tic); + return result; +} + +/** + * Returns a new, GC-free, pointer to a struct or to an union. + * + * Params: + * ST = A struct or an union type. + * a = Variadic parameters passed to the constructor. + */ +ST* construct(ST, A...)(A a) @trusted +if(is(ST==struct) || is(ST==union)) +{ + auto size = ST.sizeof; + auto memory = getMem(size); + static if (!hasUDA!(ST, NoInit)) + { + __gshared static ST init = ST.init; + void* atInit = &init; + memory[0..size] = atInit[0..size]; + } + static if (A.length) + { + static if (__traits(hasMember, ST, "__ctor")) + { + (cast(ST*) (memory)).__ctor(a); + } + else static if (A.length <= (__traits(allMembers, ST)).length) + { + import std.range: iota; + foreach(i; aliasSeqOf!(iota(0, A.length))) + { + __traits(getMember, cast(ST*) memory, __traits(allMembers, ST)[i]) = a[i]; + } + } + else static assert(0, "too much argument to generate an automatic constructor"); + } + static if (MustAddGcRange!ST) + { + import core.memory: GC; + GC.addRange(memory, size, typeid(ST)); + } + return cast(ST*) memory; +} + +/** + * Destructs a struct or a union that's been previously constructed + * with $(D construct()). + * + * The function calls the destructor and, when passed as reference, + * set the the instance to null. + * + * Params: + * T = A union or a struct type, likely to be infered. + * instance = A $(D T) instance. + */ +void destruct(T)(auto ref T* instance) +if (is(T == struct) || is(T==union)) +{ + if (!instance) + return; + static if (__traits(hasMember, T, "__xdtor")) + instance.__xdtor; + freeMem(cast(void*) instance); + instance = null; +} + +/** + * Destructs a struct or a union that's allocated within another + * aggregate that's not GC-managed. + * + * Params: + * T = A union or a struct type, likely to be infered. + * instance = A $(D T) instance. + */ +void destruct(T)(ref T instance) +if (is(T == struct)) +{ + static if (__traits(hasMember, T, "__xdtor")) + instance.__xdtor; +} + +/** + * Destructs a class that's been previously constructed with $(D construct()) + * and when the static type is known. + * + * The function calls the destructor and, when passed as reference, + * set the the instance to null. + * When the static type is not known, destruct must be called after a cast to + * Object. + * + * Params: + * assumeNoDtor = When no __ctor is found this avoids to search one + * in the base classes. + * T = A class type (most derived), likely to be infered. + * instance = A $(D T) instance. + */ +void destruct(bool assumeNoDtor = false, T)(auto ref T instance) +if (is(T == class) && T.stringof != Object.stringof) +{ + if (instance) + { + static if (__traits(hasMember, T, "__xdtor") || assumeNoDtor) + { + static if (__traits(hasMember, T, "__xdtor")) + instance.__xdtor; + freeMem(cast(void*)instance); + instance = null; + } + else // dtor might be in an ancestor + { + destruct(cast(Object) instance); + } + } +} + +/** + * Destructs a class that's been previously constructed with $(D construct()) and + * when the static type is not known. + * + * This overload is only selected when the instance is casted as Object. + * It should be used when there no guarantee that the instance type is the most + * derived. This overload is never @nogc. + * + * Params: + * instance = A class instance casted to Object. + */ +void destruct(T)(auto ref T instance) +if (is(T == class) && T.stringof == Object.stringof) +{ + if (instance) + { + TypeInfo_Class tic = cast(TypeInfo_Class) typeid(instance); + if (void* dtorPtr = tic.destructor) + { + void delegate() dtor; + dtor.funcptr = cast(void function()) dtorPtr; + dtor.ptr = cast(void*) instance; + dtor(); + } + freeMem(cast(void*)instance); + instance = null; + } +} + +/** + * Destructs an interface implemented in an Object that's been previously + * constructed with $(D construct()). + * + * This overload is never @nogc. + * + * Params: + * T = A class type, likely to be infered. + * instance = A $(D T) instance. + */ +void destruct(T)(auto ref T instance) +if (is(T == interface)) +{ + if (instance) + { + version(Windows) + { + import core.sys.windows.unknwn: IUnknown; + static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in " + ~ __PRETTY_FUNCTION__); + } + static if (__traits(allMembers, T).length) + { + bool allCpp() + { + bool result = true; + foreach (member; __traits(allMembers, T)) + foreach (ov; __traits(getOverloads, T, member)) + static if (functionLinkage!ov != "C++") + { + result = false; + break; + } + return result; + } + static assert(!allCpp, "C++ interfaces can't be destroyed in " + ~ __PRETTY_FUNCTION__); + } + destruct(cast(Object) instance); + instance = null; + } +} + +/** + * Destructs on pointers simply forwards $(D freeMem). + * + * Params: + * instance = A pointer, typed or not. + */ +void destruct(T)(auto ref T* instance) +if (isBasicType!T) +{ + if (instance) + freeMem(cast(void*) instance); + instance = null; +} + +/** + * Returns a pointer to a new, GC-free, basic variable. + * Any variable allocated using this function must be manually freed with freeMem. + * + * Params: + * T = The type of the pointer to return. + * preFill = Optional, boolean indicating if the result has to be initialized. + * a = Optional, the value. + */ +T* newPtr(T, bool preFill = false, A...)(A a) +if (isBasicType!T && A.length <= 1) +{ + T* result = cast(T*) getMem(T.sizeof); + static if (A.length == 1) + *result = a[0]; + else static if (preFill) + *result = T.init; + return result; +} + +/** + * Frees and invalidates a list of classes instances or struct pointers. + * $(D destruct()) is called for each item. + * + * Params: + * objs = Variadic list of Object instances. + */ +void destructEach(Objs...)(auto ref Objs objs) +if (Objs.length > 1) +{ + foreach(ref obj; objs) + destruct(obj); +} + +/** + * Register a class type that can be created dynamically, using its name. + * + * Class registration should only be done in module constructors. This allow + * the registration to be thread safe since module constructors are executed + * in the main thread. + * + * Params: + * T = A class. + * name = The name used to register the class. + * By default the $(D T.stringof) is used. + * f = The class repository, a hashmap of TypeInfo_Class by string. + */ +void registerFactoryClass(T, F)(ref F f, string name = "") +if (is(T == class) && !isAbstractClass!T) +{ + if (!name.length) + name = T.stringof; + f[name] = typeid(T); +} + +/** + * Calls registerClass() for each template argument. + * + * Params: + * A = A list of classes, from one up to nine. + * f = The class repository, a hashmap of TypeInfo_Class by string. + */ +void registerFactoryClasses(A1, A2 = void, A3 = void, A4 = void, A5 = void, + A6 = void, A7 = void, A8 = void, A9 = void, F)(ref F f) +{ + void tryRegister(A)() + { + static if (is(A == class)) + registerFactoryClass!(A, F)(f); + else static if (!is(A == void)) + static assert(0, A.stringof ~ " is not a class"); + } + tryRegister!A1;tryRegister!A2;tryRegister!A3; + tryRegister!A4;tryRegister!A5;tryRegister!A6; + tryRegister!A7;tryRegister!A8;tryRegister!A9; +} + +/** + * Constructs a class instance using a gc-free factory. + * The usage is similar to Object.factory() except that by default + * registered classes don't take the module in account. + * + * Params: + * className = the class name, as registered in registerFactoryClass(). + * factory = The class repository, a hashmap of TypeInfo_Class by string. + * Throws: + * An Exception if the class is not registered. + */ +Object factory(F)(ref F f, string className) +{ + TypeInfo_Class* tic = className in f; + if (!tic) + throw construct!Exception("factory exception, unregistered class"); + return + construct(*tic); +} + +unittest +{ + import core.memory: GC; + + interface I{} + class AI: I{} + + auto a = construct!Object; + a.destruct; + assert(!a); + a.destruct; + assert(!a); + a.destruct; + + auto b = construct!Object; + auto c = construct!Object; + destructEach(a,b,c); + assert(!a); + assert(!b); + assert(!c); + destructEach(a,b,c); + assert(!a); + assert(!b); + assert(!c); + + Object foo = construct!Object; + Object bar = new Object; + assert( GC.addrOf(cast(void*)foo) == null ); + assert( GC.addrOf(cast(void*)bar) != null ); + foo.destruct; + bar.destroy; + + struct Foo + { + this(size_t a,size_t b,size_t c) + { + this.a = a; + this.b = b; + this.c = c; + } + size_t a,b,c; + } + + Foo * foos = construct!Foo(1,2,3); + Foo * bars = new Foo(4,5,6); + assert(foos.a == 1); + assert(foos.b == 2); + assert(foos.c == 3); + assert( GC.addrOf(cast(void*)foos) == null ); + assert( GC.addrOf(cast(void*)bars) != null ); + foos.destruct; + bars.destroy; + assert(!foos); + foos.destruct; + assert(!foos); + + union Uni{bool b; ulong ul;} + Uni* uni0 = construct!Uni(); + Uni* uni1 = new Uni(); + assert( GC.addrOf(cast(void*)uni0) == null ); + assert( GC.addrOf(cast(void*)uni1) != null ); + uni0.destruct; + assert(!uni0); + uni0.destruct; + assert(!uni0); + + auto ai = construct!AI; + auto i = cast(I) ai; + destruct(i); + assert(i is null); + + abstract class Abst {} + Object ab = construct(typeid(Abst)); + assert(ab is null); +} + +@nogc unittest +{ + class Foo {@nogc this(int a){} @nogc~this(){}} + Foo foo = construct!Foo(0); + destruct(foo); + assert(foo is null); + + static struct Bar {} + Bar* bar = construct!Bar; + destruct(bar); + assert(bar is null); + + static struct Baz {int i; this(int,int) @nogc {}} + Baz* baz = construct!Baz(0,0); + destruct(baz); + assert(baz is null); +} + +unittest +{ + struct Foo {int a; ulong b;} + Foo* f = construct!Foo(1,2); + assert(f.a == 1); + assert(f.b == 2); + destruct(f); +} + +unittest +{ + import core.memory: GC; + import std.math: isNaN; + + auto f = newPtr!(float,true); + assert(isNaN(*f)); + auto ui = newPtr!int; + auto i = newPtr!uint; + auto l = new ulong; + auto u = newPtr!uint(8); + assert(*u == 8u); + + assert(ui); + assert(i); + assert(f); + + assert(GC.addrOf(f) == null); + assert(GC.addrOf(i) == null); + assert(GC.addrOf(ui) == null); + assert(GC.addrOf(l) != null); + assert(GC.addrOf(u) == null); + + *i = 8u; + assert(*i == 8u); + + freeMem(ui); + freeMem(i); + freeMem(f); + freeMem(u); + + assert(ui == null); + assert(i == null); + assert(f == null); + + auto ptr = getMem(16); + assert(ptr); + assert(GC.addrOf(ptr) == null); + ptr.freeMem; + assert(!ptr); +} + +unittest +{ + class A{string text; this(){text = "A";}} + class B{string text; this(){text = "B";}} + class C{int[] array; this(){array = [1,2,3];}} + enum TypeInfo_Class[3] tics = [typeid(A),typeid(B),typeid(C)]; + + A a = cast(A) construct(tics[0]); + assert(a.text == "A"); + B b = cast(B) construct(tics[1]); + assert(b.text == "B"); + C c = cast(C) construct(tics[2]); + assert(c.array == [1,2,3]); + + destructEach(a, b, c); +} + +unittest +{ + alias Factory = TypeInfo_Class[string]; + __gshared Factory classRegistry; + + class A{string text; this(){text = "A";}} + class B{string text; this(){text = "B";}} + class C{int[] array; this(){array = [1,2,3];}} + registerFactoryClass!A(classRegistry); + classRegistry.registerFactoryClass!B; + classRegistry.registerFactoryClass!C; + + registerFactoryClasses!(A,B)(classRegistry); + + A a = cast(A) classRegistry.factory("A"); + assert(a.text == "A"); + B b = cast(B) classRegistry.factory("B"); + assert(b.text == "B"); + C c = cast(C) classRegistry.factory("C"); + assert(c.array == [1,2,3]); + + version(checkleaks) {} else + { + import std.exception: assertThrown; + assertThrown(classRegistry.factory("D")); + } + + destructEach(a,b,c); +} + +@nogc unittest +{ + void* src = getMem(32); + void* dst = getMem(32); + (cast (ubyte*)src)[0..32] = 8; + copyMem(dst, src, 32); + foreach(immutable i; 0 .. 32) + assert((cast (ubyte*)src)[i] == (cast (ubyte*)dst)[i]); + (cast (ubyte*)src)[0..32] = 7; + src = reallocMem(src, 32 + 16); + ubyte* ovl = (cast (ubyte*)src) + 16; + moveMem(cast(void*)ovl, cast(void*)src, 32); + assert((cast (ubyte*)ovl)[31] == 7); + freeMem(src); + freeMem(dst); +} + +@nogc unittest +{ + @NoInit static struct Foo {uint a = 1; ~this() @nogc{}} + Foo* foo = construct!Foo; + assert(foo.a != 1); + @NoInit static class Bar {uint a = 1; ~this() @nogc{}} + Bar bar = construct!Bar; + assert(bar.a != 1); + destructEach(foo, bar); +} + +unittest +{ + static int i; + + template impl() + { + ~this(){i += 1;} + } + + static class Base + { + mixin impl; + mixin inheritedDtor; + ~this(){i += 2;} + } + + static class Derived: Base + { + mixin inheritedDtor; + ~this(){i += 3; callInheritedDtor;} + } + + Base b = construct!Derived; + // without static type + destruct(cast(Object) b); + assert(i == 6); + i = 0; + + Derived d = construct!Derived; + // with static type + destruct(d); + assert(i == 6); +} + +/** + * Generic, manually managed, array. + * + * Array(T) implements a single-dimension array of uncollected memory. + * It internally allocates memory blocks to minimize the reallocation fingerprints, + * allowing insertions to be 2 times faster than built-in arrays. + * + * Its layout differs from built-in D's dynamic arrays and they cannot be cast as T[] + * however, most of the slicing operations are possible. + * + * Manual management implies that $(D destruct()) must be called on the array when + * it goes out of scope. $(D destruct()) is only called on the content when the + * specialization is a $(D struct()) or a $(D union)). Classes and pointers must + * be freed by hand. + */ +struct Array(T) +{ + +private: + + size_t _length; + @NoGc Ptr _elems; + uint _granularity = 4096; + size_t _blockCount; + + pragma(inline, true) + void setLength(size_t value) @nogc + { + assert(_granularity != 0); + + const size_t newBlockCount = ((value * T.sizeof) / _granularity) + 1; + if (_blockCount != newBlockCount) + { + _blockCount = newBlockCount; + _elems = reallocMem(_elems, _granularity * _blockCount); + } + _length = value; + } + + pragma(inline, true) + void grow() @nogc + { + length(_length + 1); + } + + pragma(inline, true) + void shrink() @nogc + { + length(_length - 1); + } + + pragma(inline, true) + Unqual!T* rwPtr(size_t index) pure const nothrow @nogc + { + return cast(Unqual!T*) (_elems + index * T.sizeof); + } + + struct Range + { + private size_t _i; + private Array!T* _a; + /// + this(ref Array!T array, size_t index = 0) @nogc @trusted + { + _a = &array; + _i = index; + } + /// + ref T front() @nogc + { + return _a.opIndex(_i); + } + /// + void popFront() @nogc @safe + { + ++_i; + } + /// + bool empty() @nogc @safe + { + return _i >= _a._length; + } + } + + pragma(inline, true) + void postblitElements()() + { + static if (is(T == struct) && hasMember!(T, "__postblit") && isCopyable!T ) + { + foreach(i; 0.._length) + (*rwPtr(i)).__postblit(); + } + } + +public: + + /// Constructs the array with a list of T. + this(E...)(E elements) @nogc + if (is(Unqual!E == T) || is(T == E)) + { + opAssign(elements); + } + + /// Constructs the array with a D array of T. + this(E)(E[] elements...) @nogc + if (is(Unqual!E == T) || is(T == E)) + { + opAssign(elements); + } + + /// Constructs by dispatching to the existing opAssign overloads. + this(E)(auto ref E value) @nogc + { + opAssign(value); + } + + this(this) + { + Ptr old = _elems; + const size_t sz = _granularity * _blockCount; + _elems = getMem(sz); + moveMem(_elems, old, sz); + postblitElements; + } + + ~this() + { + length(0); + if (_elems) + freeMem(_elems); + _elems = null; + } + + /** + * Indicates the memory allocation block-size. + */ + uint granularity() const pure nothrow @safe @nogc + { + return _granularity; + } + + /** + * Sets the memory allocation block-size. + * value should be set to 16 or 4096 (the default). + */ + void granularity(uint value) @nogc + { + if (_granularity == value) + return; + + if (value < T.sizeof) + { + value = 16 * T.sizeof; + } + else if (value < 16) + { + value = 16; + } + else while (_granularity % 16 != 0) + { + value--; + } + _granularity = value; + setLength(_length); + } + + /** + * Indicates how many block the array is made of. + */ + pragma(inline, true) + size_t blockCount() const pure nothrow @safe @nogc + { + return _blockCount; + } + + /// Sets or gets the element count. + pragma(inline, true) + size_t length() const pure nothrow @safe @nogc + { + return _length; + } + + /// ditto + void length(V)(V value) + if (isIntegral!V) + { + if (value == _length) + return; + size_t oldLen = _length; + static if (is(T == struct) && !isTuple!T) + { + if (value < _length) + { + foreach (i; value.._length) + destruct(opIndex(i)); + } + } + setLength(value); + if (value > oldLen) + { + static if (is(T == struct) && !hasUDA!(T, NoInit)) + { + foreach (i; oldLen.._length) + emplace(rwPtr(i)); + } + else static if (std.traits.isIntegral!T) + { + memset(rwPtr(oldLen), 0, (_length - oldLen) * T.sizeof); + } + else static if (is(T == class)) + { + memset(rwPtr(oldLen), ubyte(), (_length - oldLen) * size_t.sizeof); + } + else static if (isBasicType!T) + { + (rwPtr(0))[oldLen.._length] = T.init; + } + else + { + foreach (i; oldLen.._length) + *rwPtr(i) = T.init; + } + } + } + + /** + * Pointer to the first element. + * As it's always assigned It cannot be used to determine if the array is empty. + */ + pragma(inline, true) + Ptr ptr() pure nothrow @nogc + { + return _elems; + } + + /** + * Typed pointer to the first element. + */ + pragma(inline, true) + T* typedPtr() pure nothrow @nogc + { + return cast(T*) _elems; + } + + /** + * Returns the string representation of the array. + * + * Throw: + * A ConvException if T is not converitble to string. + */ + static if (__traits(compiles, to!string(opSlice()))) + string toString() + { + if (_length == 0) + return "[]"; + else + { + auto r = opSlice(); + return to!string(r); + } + } + + /// Returns a mutable (deep) copy of the array. + Array!T dup()() + { + Array!T result = this; + return result; + } + + /// Support for associative arrays. + size_t toHash() nothrow @trusted + { + return opSlice().hashOf; + } + + /// Support for equality tests. + bool opEquals(A)(auto ref A array) const pure @nogc @trusted + if ((is(Unqual!A == Unqual!(Array!T)) || is(Unqual!(ElementEncodingType!A) == T))) + { + if (_length != array.length) + return false; + else if (_length == 0 && array.length == 0) + return true; + static if (is(Unqual!A == Unqual!(Array!T))) + return _elems[0.._length * T.sizeof] == + array._elems[0..array._length * T.sizeof]; + else + return _elems[0.._length * T.sizeof] == + array.ptr[0..array.length]; + } + + /// Support for the array syntax. + pragma(inline, true) + ref T opIndex(size_t i) const pure @nogc + { + return *rwPtr(i); + } + + /// Support for the array syntax. + void opIndexAssign()(ref T item, size_t i) @nogc + { + *rwPtr(i) = item; + } + + /// Ditto + void opIndexAssign()(T item, size_t i) @nogc + { + *rwPtr(i) = item; + } + + /// ditto + static if (isTemplateInstance!(T) && __traits(isSame, TemplateOf!T, TemplateOf!(typeof(this)))) + void opIndexAssign()(TemplateArgsOf!(T)[0][] item, size_t i) @nogc + { + *rwPtr(i) = item; + } + + /// Support for the foreach operator. + int opApply(scope int delegate(ref T) dg) + { + int result; + foreach (immutable i; 0 .. _length) + { + result = dg(*rwPtr(i)); + if (result) break; + } + return result; + } + + /// Support for the foreach_reverse operator. + int opApplyReverse(scope int delegate(ref T) dg) + { + int result; + foreach_reverse (immutable i; 0 .. _length) + { + result = dg(*rwPtr(i)); + if (result) break; + } + return result; + } + + /// Support for the dollar operator. + size_t opDollar() const pure nothrow @safe @nogc + { + return _length; + } + + /// Assign another Array!T. + void opAssign(E)(auto ref Array!E elements) @nogc + if (is(Unqual!E == T) || is(E == T)) + { + setLength(elements._length); + moveMem(_elems, elements._elems, T.sizeof * _length); + /*setLength(0); + _granularity = elements._granularity; + _length = elements.length; + _elems = elements._elems; + _blockCount = elements._blockCount; + __postblit();*/ + } + + /// Assigns a D array. + void opAssign(E)(auto ref E[] elements) @nogc + if (is(Unqual!E == T) || is(E == T)) + { + setLength(elements.length); + foreach (i, element; elements) + *rwPtr(i) = cast(T) element; + } + + /// Assigns an inpunt range. + void opAssign(E)(auto ref E elements) @nogc + if (isInputRange!E && is(Unqual!(ElementType!E) == T) && !isRandomAccessRange!E) + { + const len = walkLength(elements); + setLength(len); + foreach (immutable i; 0..len) + { + opAssign(elements.front); + elements.popFront; + } + } + + /// Support for the cat operator + auto ref typeof(this) opBinary(string op : "~", R)(auto ref R rhs) return @nogc + if (__traits(hasMember, R, "length") && __traits(hasMember, R, "ptr")) + { + typeof(this) result; + result.length = _length + rhs.length; + moveMem(result.rwPtr(0), _elems , T.sizeof * _length); + moveMem(result.rwPtr(_length), rhs._elems , T.sizeof * rhs.length); + return result; + } + + /// Support for the cat operator. + ref typeof(this) opOpAssign(string op, E = T)(auto ref E[] elements) @nogc + if (is(Unqual!E == T) || is(E == T)) + { + static if (op == "~") + { + const old = _length; + setLength(_length + elements.length); + moveMem( rwPtr(old), elements.ptr , T.sizeof * elements.length); + return this; + } + else static assert(0, "operator not implemented"); + } + + /// Support for the cat operator. + ref typeof(this) opOpAssign(string op)(T aElement) @nogc + { + static if (op == "~") + { + grow; + opIndexAssign(aElement, _length-1); + return this; + } + else static assert(0, "operator not implemented"); + } + + /// Support for output ranges. + alias put = opOpAssign!"~"; + + /// ditto + void put(E)(auto ref E[] elements) @nogc + if (is(Unqual!E == T) || is(E == T)) + { + const old = _length; + setLength(_length + elements.length); + moveMem( rwPtr(old), elements.ptr , T.sizeof * elements.length); + } + + /// Returns a slice of the array. The memory is not duplicated. + auto ref opSlice(bool dSlice = false)(size_t lo, size_t hi) return @nogc + { + static if (dSlice) + { + return (cast(T*) _elems)[lo..hi]; + } + else + { + Array!T result; + result.length = hi - lo; + moveMem(result.rwPtr(0), rwPtr(lo), (hi - lo) * T.sizeof); + return result; + } + } + + /// Returns the array as a D slice. The memory is not duplicated. + T[] opSlice() const + { + return (cast(T*) _elems)[0.._length]; + } + + /// Support for filling the array with a single element. + void opSliceAssign()(T value) @nogc + { + rwPtr(0)[0.._length] = value; + } + + /// ditto + void opSliceAssign()(T value, size_t lo, size_t hi) @nogc + { + foreach (immutable i; lo .. hi) + *rwPtr(i) = value; + } + + /// Returns an input range with an assignable front. + auto range() + { + return Range(this, 0); + } + + /// Allows to use the array as a D built-in array. + alias opSlice this; +} + +/** + * Default hash function used in the HashSet and the HashMap + */ +pragma(inline, true) +size_t fnv1(V, bool fnv1a = false)(auto ref V value) +{ + static if (isBasicType!V || is(V==struct) || is(V == union)) + return fnv1Impl!fnv1a(cast(ubyte*) &value, V.sizeof); + else static if (isArray!V) + return fnv1Impl!fnv1a(cast(ubyte*) value.ptr, value.length * (ElementEncodingType!V).sizeof); + else static if (isPointer!V || is(V == class) || is(V == interface)) + return fnv1Impl!fnv1a(cast(ubyte*) value, V.sizeof); + else static assert(0); +} + +private size_t fnv1Impl(bool fnv1a = false)(ubyte* data, size_t length) +{ + static if (size_t.sizeof == 8) + size_t h = 0xCBF29CE484222325UL; + else static if (size_t.sizeof == 4) + size_t h = 0x811C9DC5UL; + else static assert(0); + + static if (size_t.sizeof == 8) + foreach (immutable i; 0..length) + { + static if (fnv1a) + { + h ^= *data++; + h *= 0x100000001B3UL; + } + else + { + h *= 0x100000001B3UL; + h ^= *data++; + } + } + else static if (size_t.sizeof == 4) + foreach (immutable i; 0..length) + { + static if (fnv1a) + { + h ^= *data++; + h *= 0x1000193U; + } + else + { + h *= 0x1000193U; + h ^= *data++; + } + } + else static assert(0); + return h; +} + +struct ArrayBucket(K, V = void) +{ + +private: + + static if (!isMap) + { + alias ArrayT = Array!K; + } + else + { + alias Pair = Tuple!(K, V); + alias ArrayT = Array!(Pair); + } + + ArrayT _array; + +public: + + enum isMap = !is(V == void); + + ~this() @nogc + { + static if (isMap) + { + foreach (immutable i; 0.._array.length) + { + static if (is(K == struct)) + destruct(_array[i][0]); + static if (is(V == struct)) + destruct(_array[i][1]); + } + } + destruct(_array); + } + + this(this) @nogc + { + _array.__postblit; + } + + ref const(ArrayT) array() const @nogc nothrow + {return _array;} + + static if (isMap) + void insert(ref K key, ref V value) @nogc nothrow + { + _array ~= tuple(key, value); + } + else + void insert(ref K key) @nogc nothrow + { + _array ~= key; + } + + bool remove()(ref K key) + { + bool result; + foreach (immutable i; 0.._array._length) + { + static if (!isMap) + { + static if (hasElaborateSelfEquals!K) + { + if (!_array[i].opEquals(key)) + continue; + } + else + { + if (_array[i] != key) + continue; + } + } + else + { + static if (hasElaborateSelfEquals!K) + { + if (!_array[i][0].opEquals(key)) + continue; + } + else + { + if (_array[i][0] != key) + continue; + } + } + result = true; + if (i == 0) + { + if (_array.length > 1) + _array = _array[1..$]; + else + _array.length = 0; + } + else if (i == _array.length-1) + { + _array.length = _array.length - 1; + } + else _array = _array[0..i] ~ _array[i+1..$]; + break; + } + return result; + } + + pragma(inline, true) + void clear() @nogc nothrow + { + _array.length = 0; + } + + ptrdiff_t indexOfKey()(ref K key) + { + ptrdiff_t result = -1; + foreach (immutable i; 0.._array.length) + { + static if (isMap) + { + static if (hasElaborateSelfEquals!K) + { + if (_array[i][0].opEquals(key)) + { + result = i; + break; + } + } + else + { + if (_array[i][0] == key) + { + result = i; + break; + } + } + } + else + { + static if (hasElaborateSelfEquals!K) + { + if (_array[i].opEquals(key)) + { + result = i; + break; + } + } + else + { + if (_array[i] == key) + { + result = i; + break; + } + } + } + } + return result; + } + + pragma(inline, true) + K* getKey(KK)(ref KK key) + { + K* result; + if (const size_t j = _array.length) + foreach (immutable i; 0..j) + { + static if (isMap) + { + static if (hasElaborateSelfEquals!K) + { + if (_array[i][0].opEquals(key)) + { + result = &_array[i][0]; + break; + } + } + else + { + if (_array[i][0] == key) + { + result = &_array[i][0]; + break; + } + } + } + else + { + static if (hasElaborateSelfEquals!K) + { + if (_array[i].opEquals(key)) + { + result = &_array[i]; + break; + } + } + else + { + if (_array[i] == key) + { + result = &_array[i]; + break; + } + } + } + } + return result; + } + + static if (isMap) + V* getValue(KK)(ref KK key) + { + V* result; + if (const size_t j = _array.length) + foreach (immutable i; 0..j) + { + static if (hasElaborateSelfEquals!K) + { + if (_array[i][0].opEquals(key)) + { + result = &_array[i][1]; + break; + } + } + else + { + if (_array[i][0] == key) + { + result = &_array[i][1]; + break; + } + } + } + return result; + } + + static if (isMap) + ptrdiff_t indexOfValue()(ref V value) + { + ptrdiff_t result = -1; + foreach(immutable i; 0.._array.length) + { + if (_array[i][1] == value) + { + result = i; + break; + } + } + return result; + } + + size_t length() @nogc const pure nothrow {return _array.length;} +} + +/** + * Enumerates the hashset and hashmap insertion mode + */ +enum +{ + /// Buckets are already reserved. + imReserved = false, + /// Always reserves a bucket. + imExpand = true +} + + +struct HashMap_AB(K, V, alias hasherFun = fnv1) +{ + static assert (is(typeof( (){size_t r = hasherFun(K.init);} )), + "invalid hash function"); + static assert (!is(K == void), + "invalid Key type"); + static assert (!is(V == void), + "invalid Value type"); + +private: + + alias HashMapT = typeof(this); + alias BucketT = ArrayBucket!(K,V); + @NoGc Array!BucketT _buckets; + size_t _count; + + pragma(inline, true) + size_t hasher(KK)(auto ref KK key) @nogc + { + return hasherFun(key) & (_buckets.length - 1); + } + + void reHash()() + { + //_count = 0; + Array!BucketT old = _buckets; + foreach (immutable i; 0.._buckets.length) + _buckets[i].clear; + foreach (immutable i; 0..old.length) + { + foreach (immutable j; 0..old[i]._array.length) + { + const h = hasher(old[i]._array[j][0]); + _buckets[h].insert(old[i]._array[j][0], old[i]._array[j][1]); + } + } + destruct(old); + } + +public: + + ~this() @nogc + { + destruct(_buckets); + } + + /** + * Tries to insert a key-value pair in the set. + * + * Params: + * mode = If set to $(D imExpand) then reserves a slot else + * if set to $(D imReserved) assumes a previous call to $(D reserve()). + * key = The key. + * value = The value. + * Throws: + * An $(D OutOfMemoryError) if an internal call to $(D reserve()) fails. + * Returns: + * If the key is added or if it's already included then returns $(D true), + * otherwise $(D false). + */ + bool insert(alias mode = imExpand)(ref K key, auto ref V value) @nogc + if (isImplicitlyConvertible!(typeof(mode), bool)) + { + bool result; + if (!_buckets.length) + reserve(1); + if (key !in this) + { + result = true; + static if (mode) + reserve(1); + const size_t h = hasher(key); + assert(h < _buckets.length); + _buckets[h].insert(key, value); + ++_count; + } + else + { + result = true; + const size_t h = hasher(key); + assert(h < _buckets.length); + _buckets[h].remove(key); + _buckets[h].insert(key, value); + } + return result; + } + + /// ditto + bool insert(alias mode = imExpand)(K key, V value) @nogc + if (isImplicitlyConvertible!(typeof(mode), bool)) + { + return insert!mode(key, value); + } + + /** + * Tries to remove a key from the set. + * + * Returns: + * $(D true) if the key was included otherwise $(D false). + */ + bool remove()(auto ref K key) @nogc + { + const size_t h = hasher(key); + const bool result = _buckets[h].remove(key); + if (result) + --_count; + return result; + } + + /** + * Reserves buckets for at least N supplemental key-value pairs. + * + * Throws: + * An $(D OutOfMemoryError) if the reallocation fails. + * Params: + * value = The count of additional slots to reserve. + */ + void reserve()(size_t value) @nogc + { + import std.math: nextPow2; + const size_t nl = nextPow2(_count + value); + if (nl > _buckets.length) + { + _buckets.length = nl; + reHash(); + } + } + + /** + * Minimizes the memory used by the map. + * + * Throws: + * An $(D OutOfMemoryError) if the reallocation fails. + */ + void minimize()() + { + import std.math: nextPow2; + const size_t nl = nextPow2(_count-1); + + if (nl < _buckets.length) + { + Array!BucketT old = _buckets; + clear; + _buckets.length = nl; + foreach (immutable i; 0..old.length) + foreach (immutable j; 0..old[i]._array.length) + { + insert!false(old[i]._array[j][0], old[i]._array[j][1]); + } + destruct(old); + } + } + + /** + * Empties the map. + */ + void clear() @nogc + { + _buckets.length = 0; + _buckets.length = 2; + _count = 0; + } + + /** + * Retrieves the value associated to a key. + * + * Params: + * key = The key. + * Returns: + * $(D null) if the key is not present otherwise a pointer to associated value. + */ + V* opBinaryRight(string op : "in", KK)(auto ref KK key) + { + V* result; + if (_buckets.length) + result = _buckets[hasher(key)].getValue(key); + return result; + } + + /** + * Support for appending an element. + * + * Forwards $(D insert()) with a default initialized value. + * + * Params: + * key = The key to insert. + * Returns: + * If the key is added or if it's already included then returns $(D true), + * otherwise $(D false). + */ + bool opOpAssign(string op : "~")(auto ref K key) + { + return insert(key, V.init); + } + + /** + * Provides an access to the buckets. + * + * Params: + * index = The bucket index. Must be in the $(D 0..bucketCount()) range. + * Returns: + * A never null pointer to a bucket. + */ + BucketT* bucket(const size_t index) pure nothrow @nogc + { + return &_buckets[index]; + } + + /** + * Support for retrieving a value using the array syntax. + */ + auto ref V opIndex(KK)(auto ref KK key) + { + import std.stdio; + return *(key in this); + } + + /** + * Support for assigning using the array syntax. + */ + void opIndexAssign(KK)(auto ref V value, auto ref KK key) + { + insert(key, value); + } + + /** + * Support for assigning to the value when the value is itself an AA. + * + * Note that this function only exists to port code that uses the runtime AA + * with the syntax $(D aa[k_for_value][k_for_value_of_value] = stuff;). + */ + void opIndexAssign(V2, KK1, KK2)(auto ref V2 value, auto ref KK1 key1, auto ref KK2 key2) + { + if (auto v = key1 in this) + { + v.insert(key2, value); + } + else + { + V v; v.reserve(1); + v.insert(key2, value); + insert!imReserved(key1, v); + } + } + + /** + * Support for the assignment operators on a value. + */ + void opIndexOpAssign(string op, VV, KK)(auto ref VV value, auto ref KK key) + { + if (auto p = key in this) + { + mixin("(*p) " ~ op ~ "= value;"); + } + else + { + V v; + mixin("v" ~ op ~ "= value;"); + insert(key, v); + } + } + + /** + * Returns: an input range that allows to iterate the key-value pairs. + */ + auto byKeyValue() + { + return RangeForAbSet!HashMapT(&this); + } + + /** + * Returns: an input range that allows to iterate the keys. + */ + auto byKey() + { + return RangeForAbSet!(HashMapT,rngMapByKey)(&this); + } + + /** + * Returns: an input range that allows to iterate the values. + */ + auto byValue() + { + return RangeForAbSet!(HashMapT,rngMapByValue)(&this); + } + + /** + * Returns: the elements count. + */ + size_t count() pure nothrow @nogc {return _count;} + + /** + * Returns the buckets count. + */ + size_t bucketCount() pure nothrow @nogc {return _buckets.length;} + + /** + * Returns: the collisions count. + */ + size_t collisions() pure nothrow @nogc + { + size_t result; + foreach(immutable i; 0.._buckets.length) + result += _buckets[i]._array.length > 1 ? + _buckets[i]._array.length - 1 : 0; + return result; + } +} + +private struct RangeForAbSet(T, alias rngKind = rngNoMap) +{ + +private: + + T* _hashSetOrMap; + alias B = ReturnType!(T.bucket); + B _currBucket; + bool _empty; + size_t _bucketIndex; + size_t _keyIndex; + +public: + + this(T* hashSetOrMap) @nogc nothrow + { + assert(hashSetOrMap); + _hashSetOrMap = hashSetOrMap; + popFront; + } + + bool empty() @nogc nothrow + { + return _currBucket is null; + } + + void popFront() @nogc nothrow + { + if (_currBucket) + { + ++_keyIndex; + if (_keyIndex >= _currBucket.length) + { + _currBucket = null; + _keyIndex = 0; + ++_bucketIndex; + } + } + if (!_currBucket) + { + while (_bucketIndex < _hashSetOrMap._buckets.length) + { + _currBucket = (*_hashSetOrMap).bucket(_bucketIndex); + if (_currBucket.length) + break; + else + ++_bucketIndex; + } + if (_bucketIndex == _hashSetOrMap._buckets.length) + _currBucket = null; + } + } + + auto ref front() @nogc + { + static if (rngKind == rngNoMap || rngKind == rngMapByKeyValue) + return _currBucket._array[_keyIndex]; + else static if (rngKind == rngMapByKey) + return _currBucket._array[_keyIndex][0]; + else static if (rngKind == rngMapByValue) + return _currBucket._array[_keyIndex][1]; + } +} + +private enum +{ + rngNoMap, + rngMapByKey, + rngMapByValue, + rngMapByKeyValue, +} + +/** + * IFTI helper for $(D SafeAccess). + * + * Returns: + * $(D m) with the ability to safely access its members that are class + * instances. + */ +auto ref safeAccess(M)(M m) +{ + return SafeAccess!M(m); +} + +struct SafeAccess(M) +if (is(M == class)) +{ + M m; + + @disable this(); + + /** + * Instantiate. + * + * Params: + * m = An instance of the entry point type. It is usually only + * $(D null) when the constructor is used internally, to build + * the chain. + */ + this(M m) + { + this.m = m; + } + + alias m this; + + /** + * Unwrap the class instance. Usually used at the end of the chain for + * a assignation to an $(D auto) variable. + */ + alias unwrap = m; + + /// Allows cast to interfaces and classes inside the chain. + auto ref as(A)() @trusted + if (!__traits(hasMember, M, "as") && is(A == class) || is(A == interface)) + { + return SafeAccess!(A)(cast(A) m); + } + + /// Handles safe access. + auto ref opDispatch(string member, A...)(auto ref A a) + if (member != "as" || __traits(hasMember, M, "as")) + { + static if (!__traits(hasMember, m , member)) + { + pragma(msg, member); + return false; + } + else + { + alias T = typeof(__traits(getMember, m, member)); + static if (is(T == class)) + { + return (!m || !__traits(getMember, m, member)) + ? SafeAccess!T(null) + : SafeAccess!T(__traits(getMember, m, member)); + } + else static if (isFunction!T) + { + // otherwise there's a missing return statement. + alias R = ReturnType!T; + static if (!is(R == void) && + !(is(R == class) && Parameters!T.length == 0)) + pragma(msg, __FILE__ ~ "(" ~ __LINE__.stringof ~ "): error, " ~ + "only `void function`s or `class` getters can be called without unwrap"); + + static if (is(R == class)) + { + return (m is null) + ? SafeAccess!R(null) + : SafeAccess!R(__traits(getMember, m, member)(a)); + } + else + { + if (m) + __traits(getMember, m, member)(a); + } + } + else static if (isIntegral!T || is(T : bool)) + { + if (m && __traits(getMember, m, member)) + return true; + else + return false; + } + else static if ((isInputRange!T && __traits(hasMember, T, "length")) + || isArray!T) + { + if (m && __traits(getMember, m, member).length) + return true; + else + return false; + } + else static assert(false); + } + } +} + +template hasElaborateSelfEquals(T) +{ + static if (is(T == class) || is(T == struct)) + { + static if (is(T == class)) + alias B = Object; + else + alias B = Unqual!T; + static if (__traits(hasMember, T, "opEquals") + && Parameters!(T.opEquals).length == 1 + && is(Unqual!(Parameters!(T.opEquals)[0]) : B)) + enum bool hasElaborateSelfEquals = true; + else + enum bool hasElaborateSelfEquals = false; + } + else enum bool hasElaborateSelfEquals = false; +} diff --git a/dexed-d/src/mainfun.d b/dexed-d/src/mainfun.d index 5a3cf6d0..d7ae6859 100644 --- a/dexed-d/src/mainfun.d +++ b/dexed-d/src/mainfun.d @@ -3,11 +3,11 @@ module mainfun; import std.stdio, std.algorithm; import - iz.memory, iz.sugar; + iz; import core.stdc.string; import - dparse.lexer, dparse.parser, dparse.ast, dparse.rollback_allocator; + dparse.lexer, dparse.ast, dparse.rollback_allocator, parser; import common; diff --git a/dexed-d/src/parser.d b/dexed-d/src/parser.d new file mode 100644 index 00000000..0f63d4a8 --- /dev/null +++ b/dexed-d/src/parser.d @@ -0,0 +1,144 @@ +module parser; + +import + dparse.lexer, dparse.ast, dparse.parser, dparse.rollback_allocator; +import + iz; + +/** + * By default libdparse outputs errors and warnings to the standard streams. + * This function prevents that. + */ +void ignoreErrors(string, size_t, size_t, string, bool) @system {} + +/// dparse Parser with bound checks fixed +final class Parser : dparse.parser.Parser +{ + override PragmaExpression parsePragmaExpression() + { + mixin (traceEnterAndExit!(__FUNCTION__)); + auto startIndex = index; + auto node = allocator.make!PragmaExpression; + mixin(tokenCheck!"pragma"); + mixin(tokenCheck!"("); + const ident = expect(tok!"identifier"); + mixin(nullCheck!`ident`); + node.identifier = *ident; + if (currentIs(tok!",")) + { + advance(); + mixin(parseNodeQ!(`node.argumentList`, `ArgumentList`)); + } + mixin(tokenCheck!")"); + node.tokens = tokens[startIndex .. index]; + return node; + } + + override TemplateValueParameterDefault parseTemplateValueParameterDefault() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto startIndex = index; + auto node = allocator.make!TemplateValueParameterDefault; + mixin(tokenCheck!"="); + if (!moreTokens) + { + error("template argument default value expected instead of EOF"); + return null; + } + switch (current.type) + { + case tok!"__FILE__": + case tok!"__FILE_FULL_PATH__": + case tok!"__MODULE__": + case tok!"__LINE__": + case tok!"__FUNCTION__": + case tok!"__PRETTY_FUNCTION__": + node.token = advance(); + break; + default: + mixin(parseNodeQ!(`node.assignExpression`, `AssignExpression`)); + break; + } + node.tokens = tokens[startIndex .. index]; + return node; + } +} + +/** + * Params: + * parserConfig = a parser configuration. + * Returns: + * The parsed module. + */ +Module parseModule(P = .Parser)(auto ref ParserConfig parserConfig) +{ + auto parser = new P(); + with (parserConfig) + { + parser.fileName = fileName; + parser.tokens = tokens; + parser.messageFunction = messageFunction; + parser.messageDelegate = messageDelegate; + parser.allocator = allocator; + } + Module mod = parser.parseModule(); + with (parserConfig) + { + if (warningCount !is null) + *warningCount = parser.warningCount; + if (errorCount !is null) + *errorCount = parser.errorCount; + } + return mod; +} + +/** + * Params: + * tokens = The tokens parsed by dparse.lexer. + * fileName = The name of the file being parsed. + * allocator = A pointer to a rollback allocator. + * messageFuncOrDg = Either a function or a delegate that receives the parser messages. + * errorCount = An optional pointer to a variable receiving the error count. + * warningCount = An optional pointer to a variable receiving the warning count. + * Returns: + * The parsed module. + */ +Module parseModule(P = .Parser,F)(const(Token)[] tokens, string fileName, RollbackAllocator* allocator, + F messageFuncOrDg = null, uint* errorCount = null, uint* warningCount = null) +{ + static if (is(F)) + { + static if (is(F : MessageFunction)) + return ParserConfig(tokens, fileName, allocator, messageFuncOrDg, null, + errorCount, warningCount).parseModule(); + else static if (is(F : MessageDelegate)) + return ParserConfig(tokens, fileName, allocator, null, messageFuncOrDg, + errorCount, warningCount).parseModule(); + else static assert(0, "F must be a MessageFunction or a MessageDelegate"); + } + else + { + return ParserConfig(tokens, fileName, allocator, null, null, null, null).parseModule!P(); + } +} + +/** + * Produces and visits the AST for a source code. + * + * This function is used to handle the content of a MixinExpression in an + * ASTVisitor. + */ +T parseAndVisit(T : ASTVisitor, A...)(const(char)[] source, A a) +{ + import std.functional; + + RollbackAllocator allocator; + LexerConfig config = LexerConfig("", StringBehavior.source, WhitespaceBehavior.skip); + StringCache cache = StringCache(StringCache.defaultBucketCount); + const(Token)[] tokens = getTokensForParser(cast(ubyte[]) source, config, &cache); + Module mod = parseModule(tokens, "", &allocator, toDelegate(&ignoreErrors)); + T result = construct!(T)(a); + result.visit(mod); + return result; +} + diff --git a/dexed-d/src/symlist.d b/dexed-d/src/symlist.d index 0e89a203..e38c8205 100644 --- a/dexed-d/src/symlist.d +++ b/dexed-d/src/symlist.d @@ -6,11 +6,9 @@ import std.array, std.conv, std.json, std.format, std.algorithm, std.string; import - iz.memory: construct, destruct, MustAddGcRange, TellRangeAdded, NoGc; + iz; import - iz.containers : Array; -import - dparse.lexer, dparse.ast, dparse.parser, dparse.formatter : Formatter; + dparse.lexer, dparse.ast, parser, dparse.formatter : Formatter; import dparse.rollback_allocator; import @@ -450,7 +448,7 @@ static assert (!MustAddGcRange!(SymbolListBuilder!(ListFmt.Pas))); override void visit(const StaticAssertStatement){} override void visit(const StructInitializer) {} override void visit(const SynchronizedStatement){} - override void visit(const ThrowStatement) {} + override void visit(const ThrowExpression) {} override void visit(const Type) {} override void visit(const Type2) {} } diff --git a/etc/iz b/etc/iz deleted file mode 160000 index adc010f1..00000000 --- a/etc/iz +++ /dev/null @@ -1 +0,0 @@ -Subproject commit adc010f1d890a9cf41fa5b2308a89852eb8c6bb8 diff --git a/etc/libdparse b/etc/libdparse index b2c88004..b94a1573 160000 --- a/etc/libdparse +++ b/etc/libdparse @@ -1 +1 @@ -Subproject commit b2c880042aeee29e72a07569bec23999c2233d47 +Subproject commit b94a1573acc5c5c6b21794970af5907f2a21822d diff --git a/src/u_synmemo.pas b/src/u_synmemo.pas index c98a5fb9..e6729229 100644 --- a/src/u_synmemo.pas +++ b/src/u_synmemo.pas @@ -12,7 +12,7 @@ uses md5, Spin, LCLIntf, LazFileUtils, LMessages, SynHighlighterCpp, math, SynGutterBase, LCLVersion, //SynEditMarkupFoldColoring, - Clipbrd, fpjson, jsonparser, LazUTF8, LazUTF8Classes, Buttons, StdCtrls, + Clipbrd, fpjson, jsonparser, LazUTF8, Buttons, StdCtrls, u_common, u_writableComponent, u_d2syn, u_txtsyn, u_dialogs, u_sxsyn, u_sharedres, u_dlang, u_stringrange, u_dbgitf, u_observer, u_diff, u_processes, u_synmultiguttermarks; @@ -2561,12 +2561,12 @@ procedure TDexedMemo.sortSelectedLines(descending, caseSensitive: boolean); var i,j: integer; lne: string; - lst: TStringListUTF8; + lst: TStringList; pt0: TPoint; begin if BlockEnd.Y - BlockBegin.Y < 1 then exit; - lst := TStringListUTF8.Create; + lst := TStringList.Create; try BeginUndoBlock; for i:= BlockBegin.Y-1 to BlockEnd.Y-1 do