1 /**
2     Stacks
3 
4     Copyright:
5         Copyright © 2023-2025, Kitsunebi Games
6         Copyright © 2023-2025, Inochi2D Project
7     
8     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9     Authors:   Luna Nielsen
10 */
11 module nulib.collections.stack;
12 import nulib.collections.internal.marray;
13 import numem.core.exception : enforce;
14 import numem.core.hooks;
15 import numem.core.traits;
16 import numem;
17 
18 /**
19     A stack which owns the memory it contains.
20 */
21 alias stack(T) = StackImpl!(T, true);
22 
23 /**
24     A stack which does not own the memory it contains.
25 */
26 alias weak_stack(T) = StackImpl!(T, false);
27 
28 /**
29     A @nogc dynamic stack
30 */
31 struct StackImpl(T, bool ownsMemory = true) {
32 @nogc:
33 private:
34     alias SelfType = typeof(this);
35     ManagedArray!(T, ownsMemory) memory;
36 
37 public:
38 
39     /**
40         Gets the internal memory slice.
41     */
42     @property T[] values() @trusted nothrow pure { return memory; }
43 
44     /**
45         The length of the vector
46     */
47     @property size_t length() const @safe nothrow { return memory.length; }
48 
49     /**
50         The capacity of the vector.
51     */
52     @property size_t capacity() const @safe nothrow { return memory.capacity; }
53 
54     /**
55         Whether the vector is empty.
56     */
57     @property bool empty() const @safe nothrow { return memory.length == 0; }
58 
59     ~this() {
60 
61         // This essentially frees the memory.
62         memory.reserve(0);
63         memory.capacity = 0;
64     }
65 
66     /**
67         Clears the vector, removing all elements from it.
68     */
69     void clear() @safe {
70         memory.reserve(0);
71     }
72 
73     /**
74         Pushes an element onto the stack.
75 
76         Params:
77             value = The value to push.
78     */
79     void push(T value) {
80         memory.resize(length+1);
81 
82         static if (hasElaborateMove!T) {
83             this.memory[$-1] = value.move();
84         } else {
85             this.memory[$-1] = value;
86         }
87     }
88 
89     /// ditto
90     void opOpAssign(string op = "~")(auto ref T value) @trusted {
91         memory.resize(length+1);
92 
93         static if (hasElaborateMove!T) {
94             this.memory[$-1] = value.move();
95         } else {
96             this.memory[$-1] = value;
97         }
98     }
99 
100     /**
101         Append a range to the stack.
102 
103         Params:
104             other = the other range to append.
105     */
106     void opOpAssign(string op = "~")(T[] other) @trusted {
107         size_t start = memory.length;
108 
109         // Self-intersecting move.
110         if (memory.isMovingIntoSelf(other)) {
111             other = rangeCopy(other);
112             memory.resize(memory.length+other.length);
113             memory.moveRange(memory[start..$], other[0..$]);
114             other = other.nu_resize(0);
115         }
116 
117         // Basic move.
118         memory.resize(memory.length+other.length);
119         memory.moveRange(memory[start..$], other[0..$]);
120     }
121 
122     /**
123         Pops a value from the stack.
124 
125         Returns:
126             The value popped.
127 
128         Throws:
129             An exception if the stack is empty.
130     */
131     T pop() {
132         enforce(memory.length > 0, "Stack is empty.");
133         
134         T tmp = memory[$-1].move();
135         memory.resize(length-1);
136         return tmp;
137     }
138 
139     /**
140         Attempts to pop a value from the stack.
141 
142         Params:
143             dst =   The variable to store the popped value in.
144 
145         Returns:
146             Whether the operation succeeded.
147     */
148     bool tryPop(ref T dst) {
149         if (memory.length == 0)
150             return false;
151         
152         dst = memory[$-1].move();
153         memory.resize(length-1);
154         return true;
155     }
156 
157     /**
158         Peeks a value in the stack.
159 
160         Params:
161             offset = The offset from the top of the stack to peek.
162         
163         Returns:
164             The value peeked.
165 
166         Throws:
167             An exception if an out-of-range access is attempted.
168     */
169     T peek(size_t offset) {
170         enforce(memory.length-offset < memory.length, "Out of range access.");
171         return memory[$-1];
172     }
173 
174     /**
175         Attempts to peek a value from the stack.
176 
177         Params:
178             offset = The offset from the top of the stack to peek.
179             dst =   The variable to store the peeked value in.
180 
181         Returns:
182             Whether the operation succeeded.
183     */
184     bool tryPeek(size_t offset, ref T dst) {
185         if (memory.length-offset > memory.length)
186             return false;
187         
188         dst = memory[$-1];
189         return true;
190     }
191 }
192 
193 @(".push(value)")
194 unittest {
195     stack!int values;
196     values.push(1);
197     values.push(2);
198     values.push(3);
199 
200     assert(values.values == [1, 2, 3]);
201 }
202 
203 @(".pop()")
204 unittest {
205     stack!int values;
206     values.push(1);
207     values.push(2);
208     values.push(3);
209 
210     assert(values.values == [1, 2, 3]);
211     assert(values.pop() == 3);
212     assert(values.values == [1, 2]);
213 }
214 
215 @(".peek(offset)")
216 unittest {
217     stack!int values;
218     values.push(1);
219     values.push(2);
220     values.push(3);
221 
222     assert(values.values == [1, 2, 3]);
223     assert(values.peek(1) == 3);
224     assert(values.values == [1, 2, 3]);
225 }