/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * This module defines primitives for working with ranges. * * Copyright: Eugen Wissner 2017-2026. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * Mozilla Public License, v. 2.0). */ module tanya.range.primitive; import std.algorithm.comparison; import std.range : isInfinite, hasSlicing, hasLvalueElements, isInputRange, isBidirectionalRange; import std.traits; import tanya.memory.lifetime; import tanya.meta; import tanya.range.array; /** * Returns the element type of the range $(D_PARAM R). * * Element type is the return type of such primitives like * $(D_INLINECODE R.front) and (D_INLINECODE R.back) or the array base type. * If $(D_PARAM R) is not a range, its element type is $(D_KEYWORD void). * * If $(D_PARAM R) is a string, $(D_PSYMBOL ElementType) doesn't distinguish * between narrow and wide strings, it just returns the base type of the * underlying array ($(D_KEYWORD char), $(D_KEYWORD wchar) or * $(D_KEYWORD dchar)). * * Params: * R = Range type. * * Returns: Element type of the range $(D_PARAM R). */ template ElementType(R) { static if (is(R U : U[])) { alias ElementType = U; } else static if (isInputRange!R) { alias ElementType = ReturnType!((R r) => r.front()); } else { alias ElementType = void; } } /** * Detects whether $(D_PARAM R) has a length property. * * $(D_PARAM R) does not have to be a range to support the length. * * Length mustn't be a $(D_KEYWORD @property) or a function, it can be a member * variable or $(D_KEYWORD enum). But its type (or the type returned by the * appropriate function) should be $(D_KEYWORD size_t), otherwise * $(D_PSYMBOL hasLength) is $(D_KEYWORD false). * * All dynamic arrays except $(D_KEYWORD void)-arrays have length. * * Params: * R = A type. * * Returns: $(D_KEYWORD true) if $(D_PARAM R) has a length property, * $(D_KEYWORD false) otherwise. * * See_Also: $(D_PSYMBOL isInfinite). */ enum bool hasLength(R) = is(ReturnType!((R r) => r.length) == size_t); /// @nogc nothrow pure @safe unittest { static assert(hasLength!(char[])); static assert(hasLength!(int[])); static assert(hasLength!(const(int)[])); struct A { enum size_t length = 1; } static assert(hasLength!(A)); struct B { @property size_t length() const @nogc nothrow pure @safe { return 0; } } static assert(hasLength!(B)); struct C { @property const(size_t) length() const @nogc nothrow pure @safe { return 0; } } static assert(!hasLength!C); } private template isDynamicArrayRange(R) { static if (is(R E : E[])) { enum bool isDynamicArrayRange = !is(E == void); } else { enum bool isDynamicArrayRange = false; } } private struct Primitive(Candidate, string primitive) { auto ref returnType(ref Candidate candidate) { mixin("return candidate." ~ primitive ~ ";"); } alias ReturnType = .ReturnType!returnType; static assert(!is(ReturnType == void)); enum uint attributes = functionAttributes!returnType & FunctionAttribute.ref_; bool opEquals(That)(That) const { return is(ReturnType == That.ReturnType) && attributes == That.attributes; } } /** * Returns the first element and advances the range. * * If $(D_PARAM range) has lvalue elements, then $(D_PSYMBOL getAndPopFront) * returns by reference, otherwise the returned element is copied. * * Params: * R = Input range type. * range = Input range. * * Returns: Front range element. * * See_Also: $(D_PSYMBOL getAndPopBack). */ ElementType!R getAndPopFront(R)(ref R range) if (isInputRange!R) in { assert(!range.empty); } do { static if (hasLvalueElements!R) { if (false) { // This code is removed by the compiler but ensures that // this function isn't @safe if range.front isn't @safe. auto _ = range.front(); } auto el = (() @trusted => &range.front())(); } else { auto el = range.front; } range.popFront(); static if (hasLvalueElements!R) { return *el; } else { return el; } } /// @nogc nothrow pure @safe unittest { int[3] array = [1, 2, 3]; auto slice = array[]; assert(getAndPopFront(slice) == 1); assert(slice.length == 2); } /** * Returns the last element and removes it from the range. * * If $(D_PARAM range) has lvalue elements, then $(D_PSYMBOL getAndPopBack) * returns by reference, otherwise the returned element is copied. * * Params: * R = Bidirectional range type. * range = Bidirectional range. * * Returns: Last range element. * * See_Also: $(D_PSYMBOL getAndPopFront). */ auto ref getAndPopBack(R)(ref R range) if (isBidirectionalRange!R) in { assert(!range.empty); } do { static if (hasLvalueElements!R) { if (false) { // This code is removed by the compiler but ensures that // this function isn't @safe if range.back isn't @safe. auto _ = range.back(); } auto el = (() @trusted => &range.back())(); } else { auto el = range.back; } range.popBack(); static if (hasLvalueElements!R) { return *el; } else { return el; } } /// @nogc nothrow pure @trusted unittest { int[3] array = [1, 2, 3]; auto slice = array[]; assert(getAndPopBack(slice) == 3); assert(slice.length == 2); }