Billy Barrow před 1 týdnem
rodič
revize
bfdbcc7d41

+ 146 - 0
src/lib/DataStructures/Lifo.vala

@@ -0,0 +1,146 @@
+using Invercargill;
+
+namespace Invercargill.DataStructures {
+
+    public class Lifo<T> : Enumerable<T>, Queue<T> {
+
+        
+        public bool is_unblocked { get { return !is_blocking; } }
+        
+        private bool is_blocking = true;
+        private LifoItem<T>* top_item = null;
+
+        public override int? peek_count() {
+            return null;
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_ultimate (this, EnumerableCategory.EXTERNAL);
+        }
+
+        public void unblock() {
+            mutex.lock();
+            is_blocking = false;
+            cond.broadcast();
+            mutex.unlock();
+        }
+
+        public void push(owned T item) {
+            mutex.lock();
+            LifoItem<T>* lifo_item = new LifoItem<T>() {
+                item = item,
+            };
+
+            if(top_item == null) {
+                top_item = lifo_item;
+            }
+            else {
+                lifo_item->next_item = top_item;
+                top_item = lifo_item;
+            }
+
+            cond.broadcast();
+            mutex.unlock();
+        }
+
+        public void push_all(Enumerable<T> items) {
+             mutex.lock();
+
+             foreach (var item in items) {
+                LifoItem<T>* lifo_item = new LifoItem<T>() {
+                    item = item,
+                };
+    
+                if(top_item == null) {
+                    top_item = lifo_item;
+                }
+                else {
+                    lifo_item->next_item = top_item;
+                    top_item = lifo_item;
+                }
+             }
+
+            cond.broadcast();
+            mutex.unlock();
+        }
+
+        public T pop() throws SequenceError {
+            mutex.lock();
+            while(is_blocking && top_item == null){
+                cond.wait(mutex);
+            }
+
+            var item = top_item;
+            if(item != null) {
+                top_item = item->next_item; 
+            }
+            mutex.unlock();
+            if(item == null) {
+                throw new SequenceError.NO_ELEMENTS("No elements in unblocked stack to pop");
+            }
+            var result = item->item;
+            delete item;
+            return result;
+        }
+
+        public T peek() throws SequenceError {
+            var item = top_item;
+            if(item == null) {
+                throw new SequenceError.NO_ELEMENTS("No elements in stack");
+            }
+            return item->item;
+        }
+
+        public void push_start(owned T item) {
+            // For a LIFO, push_start is the same as push
+            push(item);
+        }
+
+        private bool has_next() {
+            mutex.lock();
+            while(is_blocking && top_item == null){
+                cond.wait(mutex);
+            }
+
+            var state = top_item != null;
+            mutex.unlock();
+            return state;
+        }
+
+        private T get_next() {
+            mutex.lock();
+            var item = top_item;
+            top_item = item->next_item;            
+            mutex.unlock();
+            var result = item->item;
+            delete item;
+            return result;
+        }
+
+        private Cond cond = Cond ();
+        private Mutex mutex = Mutex ();
+
+        public override Tracker<T> get_tracker () {
+
+            return new LambdaTracker<T>(
+                () => has_next(),
+                () => get_next());
+        }
+
+        ~Lifo() {
+            var n = top_item;
+            while(n != null) {
+                var c = n;
+                n = n->next_item;
+                delete c;
+            }
+        }
+
+        [Compact]
+        private class LifoItem<T> {
+            public T item;
+            public LifoItem<T>* next_item;
+        }
+
+    }
+}

+ 1 - 0
src/lib/meson.build

@@ -102,6 +102,7 @@ sources += files('Interfaces/Queue.vala')
 
 sources += files('DataStructures/Series.vala')
 sources += files('DataStructures/Fifo.vala')
+sources += files('DataStructures/Lifo.vala')
 sources += files('DataStructures/Vector.vala')
 sources += files('DataStructures/HashSet.vala')
 sources += files('DataStructures/Dictionary.vala')

+ 212 - 0
src/tests/Integration/Lifo.vala

@@ -0,0 +1,212 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+void lifo_tests() {
+
+    Test.add_func("/invercargill/lifo/basic_push_pop", () => {
+        var lifo = new Lifo<string>();
+        
+        // Push items
+        lifo.push("first");
+        lifo.push("second");
+        lifo.push("third");
+        
+        // Pop in reverse order (LIFO)
+        var first = lifo.pop();
+        var second = lifo.pop();
+        var third = lifo.pop();
+        
+        assert_cmpstr("third", CompareOperator.EQ, first);
+        assert_cmpstr("second", CompareOperator.EQ, second);
+        assert_cmpstr("first", CompareOperator.EQ, third);
+    });
+
+    Test.add_func("/invercargill/lifo/peek functionality", () => {
+        var lifo = new Lifo<int>();
+        
+        lifo.push(1);
+        lifo.push(2);
+        lifo.push(3);
+        
+        // Peek should return top item without removing
+        var top = lifo.peek();
+        assert_cmpint(3, CompareOperator.EQ, top);
+        
+        // Pop should still return 3
+        var popped = lifo.pop();
+        assert_cmpint(3, CompareOperator.EQ, popped);
+        
+        // Peek now should return 2
+        var new_top = lifo.peek();
+        assert_cmpint(2, CompareOperator.EQ, new_top);
+    });
+
+    Test.add_func("/invercargill/lifo/empty_lifo_behavior", () => {
+        // Test peek on empty lifo
+        var lifo1 = new Lifo<string>();
+        try {
+            lifo1.peek();
+            assert_not_reached();
+        }
+        catch (SequenceError e) {
+            assert_cmpstr("No elements in stack", CompareOperator.EQ, e.message);
+        }
+        
+        // Test pop on empty lifo - need to unblock first
+        var lifo2 = new Lifo<string>();
+        lifo2.unblock();
+        try {
+            lifo2.pop();
+            assert_not_reached();
+        }
+        catch (SequenceError e) {
+            assert_cmpstr("No elements in unblocked stack to pop", CompareOperator.EQ, e.message);
+        }
+    });
+
+    Test.add_func("/invercargill/lifo/push_all", () => {
+        var lifo = new Lifo<int>();
+        var items = new Series<int>();
+        items.add(1);
+        items.add(2);
+        items.add(3);
+        
+        // Push all items
+        lifo.push_all(items);
+        
+        // Should pop in reverse order
+        assert_cmpint(3, CompareOperator.EQ, lifo.pop());
+        assert_cmpint(2, CompareOperator.EQ, lifo.pop());
+        assert_cmpint(1, CompareOperator.EQ, lifo.pop());
+    });
+
+    Test.add_func("/invercargill/lifo/enumerable_behavior", () => {
+        var lifo = new Lifo<string>();
+        
+        lifo.push("first");
+        lifo.push("second");
+        lifo.push("third");
+        lifo.unblock();
+        
+        // Iteration should return items in LIFO order
+        var tracker = lifo.get_tracker();
+        var items = new string[3];
+        var index = 0;
+        
+        while (tracker.has_next()) {
+            items[index++] = tracker.get_next();
+        }
+        
+        assert_cmpstr("third", CompareOperator.EQ, items[0]);
+        assert_cmpstr("second", CompareOperator.EQ, items[1]);
+        assert_cmpstr("first", CompareOperator.EQ, items[2]);
+    });
+
+    Test.add_func("/invercargill/lifo/blocking_behavior", () => {
+        var lifo = new Lifo<string>();
+        bool thread_started = false;
+        bool thread_finished = false;
+        
+        // Start a thread that will wait for an item
+        Thread<void*> thread = new Thread<void*>("test-thread", () => {
+            thread_started = true;
+            var item = lifo.pop();
+            assert_cmpstr("test_item", CompareOperator.EQ, item);
+            thread_finished = true;
+            return null;
+        });
+        
+        // Wait for thread to start and block
+        while (!thread_started) {
+            Thread.usleep(1000); // 1ms
+        }
+        // Give additional time for thread to block on pop()
+        Thread.usleep(50000); // 50ms
+        
+        // Push an item to unblock the thread
+        lifo.push("test_item");
+        
+        // Wait for thread to finish
+        thread.join();
+        assert_true(thread_finished);
+    });
+
+    Test.add_func("/invercargill/lifo/unblock_behavior", () => {
+        var lifo = new Lifo<int>();
+        bool thread_started = false;
+        bool exception_thrown = false;
+        
+        // Start a thread that will wait for an item
+        Thread<void*> thread = new Thread<void*>("test-thread", () => {
+            thread_started = true;
+            try {
+                var item = lifo.pop(); // This should throw after unblock
+                assert_not_reached();
+            }
+            catch (SequenceError e) {
+                assert_cmpstr("No elements in unblocked stack to pop", CompareOperator.EQ, e.message);
+                exception_thrown = true;
+            }
+            return null;
+        });
+        
+        // Wait for thread to start and block
+        while (!thread_started) {
+            Thread.usleep(1000); // 1ms
+        }
+        // Give additional time for thread to block on pop()
+        Thread.usleep(50000); // 50ms
+        
+        // Unblock the lifo
+        lifo.unblock();
+        
+        // Wait for thread to finish
+        thread.join();
+        assert_true(exception_thrown);
+    });
+
+    Test.add_func("/invercargill/lifo/try_peek_and_try_pop", () => {
+        var lifo = new Lifo<string>();
+        
+        // Try operations on empty lifo
+        string item;
+        assert_false(lifo.try_peek(out item));
+        assert_null(item);
+        
+        string popped;
+        lifo.unblock();
+        assert_false(lifo.try_pop(out popped));
+        assert_null(popped);
+        
+        // Add an item
+        lifo.push("test");
+        
+        // Try operations should now succeed
+        assert_true(lifo.try_peek(out item));
+        assert_cmpstr("test", CompareOperator.EQ, item);
+        
+        assert_true(lifo.try_pop(out popped));
+        assert_cmpstr("test", CompareOperator.EQ, popped);
+        
+        // Should be empty again
+        assert_false(lifo.try_pop(out popped));
+        assert_null(popped);
+    });
+
+    Test.add_func("/invercargill/lifo/push_start", () => {
+        var lifo = new Lifo<string>();
+        
+        // Add items normally
+        lifo.push("first");
+        lifo.push("second");
+        
+        // Add item to start (same as push for LIFO)
+        lifo.push_start("zero");
+        
+        // Should pop: zero, second, first
+        assert_cmpstr("zero", CompareOperator.EQ, lifo.pop());
+        assert_cmpstr("second", CompareOperator.EQ, lifo.pop());
+        assert_cmpstr("first", CompareOperator.EQ, lifo.pop());
+    });
+
+}

+ 1 - 0
src/tests/TestRunner.vala

@@ -24,6 +24,7 @@ public static int main(string[] args) {
     order_by_tests();
     set_tests();
     fifo_tests();
+    lifo_tests();
 
     var result = Test.run();
     

+ 1 - 0
src/tests/meson.build

@@ -23,6 +23,7 @@ sources += files('Integration/SortedSeries.vala')
 sources += files('Integration/OrderBy.vala')
 sources += files('Integration/Set.vala')
 sources += files('Integration/Fifo.vala')
+sources += files('Integration/Lifo.vala')
 
 sources += files('Speed/SpeedTest.vala')
 sources += files('Speed/Series.vala')