diff options
| -rw-r--r-- | .clangd | 1 | ||||
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Makefile | 10 | ||||
| -rw-r--r-- | compile_commands.json | 40 | ||||
| -rw-r--r-- | ls_queue.h | 167 | ||||
| -rw-r--r-- | queue.h | 64 | ||||
| -rw-r--r-- | tests/tests.c | 122 |
7 files changed, 291 insertions, 116 deletions
@@ -1,2 +1,3 @@ CompileFlags: CompilationDatabase: . + Add: [-x, c] @@ -1,3 +1,4 @@ tests/tests .cache/ -queue.o +ls_queue.o +compile_commands.json @@ -1,11 +1,11 @@ -tests/tests: queue.o tests/tests.c tests/ls_test.h - $(CC) -o $@ queue.o tests/tests.c -Itests -I. +tests/tests: ls_queue.o tests/tests.c tests/ls_test.h + $(CC) -o $@ ls_queue.o tests/tests.c -Itests -I. # Usually you wouldn't do this, but for tests we want this compiled with the # most pedantic settings. # Dont use this. -queue.o: queue.h - $(CC) -c -x c -o $@ $^ -Wall -Wextra -Wpedantic -Werror -ansi \-std=c89 \ +ls_queue.o: ls_queue.h + $(CC) -c -x c -o $@ $^ -Wall -Wextra -Wpedantic -Werror -ansi -std=c89 \ -DLS_QUEUE_IMPLEMENTATION \ -Wno-error=pragma-once-outside-header \ -Wno-pragma-once-outside-header @@ -14,4 +14,4 @@ queue.o: queue.h clean: rm -f tests/tests - rm -f queue.o + rm -f ls_queue.o diff --git a/compile_commands.json b/compile_commands.json deleted file mode 100644 index e0ef177..0000000 --- a/compile_commands.json +++ /dev/null @@ -1,40 +0,0 @@ -[ - { - "arguments": [ - "/usr/bin/cc", - "-c", - "-x", - "c", - "-Wall", - "-Wextra", - "-Wpedantic", - "-Werror", - "-ansi", - "-pedantic", - "-DLS_QUEUE_IMPLEMENTATION", - "-Wno-error=pragma-once-outside-header", - "-Wno-pragma-once-outside-header", - "-o", - "queue.o", - "queue.h" - ], - "directory": "/home/lion/src/ls/ls_queue", - "file": "/home/lion/src/ls/ls_queue/queue.h", - "output": "/home/lion/src/ls/ls_queue/queue.o" - }, - { - "arguments": [ - "/usr/bin/cc", - "-c", - "queue.o", - "-Itests", - "-I.", - "-o", - "tests/tests", - "tests/tests.c" - ], - "directory": "/home/lion/src/ls/ls_queue", - "file": "/home/lion/src/ls/ls_queue/tests/tests.c", - "output": "/home/lion/src/ls/ls_queue/tests/tests" - } -] diff --git a/ls_queue.h b/ls_queue.h new file mode 100644 index 0000000..0e5fb8f --- /dev/null +++ b/ls_queue.h @@ -0,0 +1,167 @@ +#pragma once + +/* Lion's Standard (LS) type-safe ANSI C queue. + * + * Version: 1.0 + * Repo: https://github.com/lionkor/ls_queue + * SPDX-License-Identifier: MIT + * + * ==== TABLE OF CONTENTS ==== + * + * 1. DESCRIPTION + * 2. HOW TO USE + * 3. LICENSE + * + * ==== 1. DESCRIPTION ==== + * + * A minimal, terse, generic (macro code generated) header-only library in ANSI + * C, which implements a queue. + * + * The implementation does not allocate, and uses a ring-buffer (aka a circular + * buffer) to avoid copying and moving memory. + * + * ==== 2. HOW TO USE ==== + * + * Statically sized, type-safe queue. + * + * Use LS_QUEUE_INLINE to generate a static inline version of the library. + * This is the "default" behavior. + * + * If you need a declaration and implementation separately, use + * LS_QUEUE_DECL and make sure to call it with the same arguments as + * LS_QUEUE_IMPL. Put LS_QUEUE_DECL in a header, and LS_QUEUE_IMPL in exactly + * ONE source file. + * + * Simple example: + * + * LS_QUEUE_TYPE_INLINE(int, int_queue, 32) + * + * // somewhere in the same file + * int_queue q; + * int_queue_init(&q); + * int_queue_push(&q, 42); + * int val; + * if (int_queue_pop(&q, &val)) { + * // do something with val + * } + * + * Alternative example with decl and inline split: + * + * // In your header file: + * LS_QUEUE_CAP_DECL(int, int_queue, 32) + * + * // In one source file: + * LS_QUEUE_CAP_IMPL(int, int_queue, 32) + * + * // Usage in your code: + * int_queue q; + * int_queue_init(&q); + * if (!int_queue_push(&q, 42)) { + * // handle queue full + * } + * int val; + * if (int_queue_pop(&q, &val)) { + * // do something with val + * } + * + * ==== 3. LICENSE ==== + * + * This file is provided under the MIT license. For commercial support and + * maintenance, feel free to use the e-mail below to contact the author(s). + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Lion Kortlepel <[email protected]> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <assert.h> +#include <stddef.h> + +#define LS_QUEUE_TYPE_INLINE(T, name, cap) \ + typedef struct name##_##cap { \ + T data[(cap) + 1]; \ + size_t read; \ + size_t write; \ + } name; \ + _ls_QUEUE_TYPE_IMPL_DETAIL(T, name, cap, static inline) + +#define LS_QUEUE_TYPE_IMPL(T, name, cap) \ + _ls_QUEUE_TYPE_IMPL_DETAIL(T, name, cap, ) + +#define LS_QUEUE_TYPE_DECL(T, name, cap) \ + typedef struct name##_##cap { \ + T data[(cap) + 1]; \ + size_t read; \ + size_t write; \ + } name; \ + void name##_init(name* q); \ + /* Returns 0 if full, 1 if successful. */ \ + int name##_push(name* q, T val); \ + /* Returns 0 if empty, 1 if successful. */ \ + int name##_pop(name* q, T* out); + +/* DO NOT USE. Use LS_QUEUE_TYPE_INLINE or LS_QUEUE_TYPE_{IMPL,DECL} instead. + * + * What follows is some ramblings about the implementation. + * + * You might notice that the queue struct has two different names, once the + * normal name, e.g. `int_queue`, and once a name with size, `struct + * int_queue_16`. You might further notice that the functions take the sized + * version, not the typedef'd version. + * + * While this might seem odd, its essentially a poor-man's way to ensure that, + * if two different int_queue structs are declared, only the one that is defined + * for will work, and the error should make it painfully clear. For example: + * + * > In included file: typedef redefinition with different types ('struct + * int_queue_18' vs 'struct int_queue_16') (clang + * redefinition_different_typedef) + */ +#define _ls_QUEUE_TYPE_IMPL_DETAIL(T, name, cap, specifier) \ + specifier void name##_init(struct name##_##cap* q) { \ + assert(q != NULL); \ + q->read = 0; \ + q->write = 0; \ + } \ + /* Returns 0 if full, 1 if successful. */ \ + specifier int name##_push(struct name##_##cap* q, T val) { \ + assert(q != NULL); \ + size_t size = sizeof(q->data) / sizeof(T); \ + size_t new_write = (q->write + 1) % size; \ + if (new_write == q->read) { \ + return 0; \ + } \ + q->data[q->write] = val; \ + q->write = new_write; \ + return 1; \ + } \ + /* Returns 0 if empty, 1 if successful. */ \ + specifier int name##_pop(struct name##_##cap* q, T* out) { \ + assert(q != NULL); \ + assert(out != NULL); \ + size_t size = sizeof(q->data) / sizeof(T); \ + if (q->read == q->write) { \ + return 0; \ + } \ + *out = q->data[q->read]; \ + q->read = (q->read + 1) % size; \ + return 1; \ + } diff --git a/queue.h b/queue.h deleted file mode 100644 index ad4230c..0000000 --- a/queue.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -/* Lion's Standard (LS) ANSI C ring buffer queue. - * - * Version: 1.0 - * Repo: https://github.com/lionkor/ls_queue - * SPDX-License-Identifier: MIT - * - * ==== TABLE OF CONTENTS ==== - * - * 1. DESCRIPTION - * 2. HOW TO USE - * 3. LICENSE - * - * ==== 1. DESCRIPTION ==== - * - * TODO - * - * ==== 2. HOW TO USE ==== - * - * 1. Copy this file into your project and include it: - * - * #include "ls_queue.h" - * - * 2. In ONE C file, define LS_QUEUE_IMPLEMENTATION before including: - * - * #define LS_QUEUE_IMPLEMENTATION - * #include "ls_queue.h" - * - * 3. TODO - * - * TODO - * - * ==== 3. LICENSE ==== - * - * This file is provided under the MIT license. For commercial support and - * maintenance, feel free to use the e-mail below to contact the author(s). - * - * The MIT License (MIT) - * - * Copyright (c) 2026 Lion Kortlepel <[email protected]> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the “Software”), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -typedef struct ls_queue { - int a; -} ls_queue; diff --git a/tests/tests.c b/tests/tests.c index 0d57df4..5edb0dc 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -1,14 +1,124 @@ #define LS_TEST_IMPLEMENTATION #include "ls_test.h" -int add(int a, int b) { - return a + b; +#include "ls_queue.h" + +LS_QUEUE_TYPE_INLINE(int, int_queue, 4) + +TEST_CASE(queue_init) { + int_queue q; + int_queue_init(&q); + ASSERT_EQ(q.read, 0, "%zu"); + ASSERT_EQ(q.write, 0, "%zu"); + return 0; +} + +TEST_CASE(queue_push_single) { + int_queue q; + int_queue_init(&q); + + int result = int_queue_push(&q, 42); + ASSERT_EQ(result, 1, "%d"); + ASSERT_EQ(q.write, 1, "%zu"); + ASSERT_EQ(q.read, 0, "%zu"); + + return 0; +} + +TEST_CASE(queue_pop_single) { + int_queue q; + int_queue_init(&q); + + int_queue_push(&q, 42); + + int val; + int result = int_queue_pop(&q, &val); + ASSERT_EQ(result, 1, "%d"); + ASSERT_EQ(val, 42, "%d"); + ASSERT_EQ(q.read, 1, "%zu"); + + return 0; +} + +TEST_CASE(queue_pop_empty) { + int_queue q; + int_queue_init(&q); + + int val; + int result = int_queue_pop(&q, &val); + ASSERT_EQ(result, 0, "%d"); + + return 0; } -TEST_CASE(add) { - ASSERT_EQ(add(1, 2), 3, "%d"); - ASSERT_EQ(add(2, 3), 5, "%d"); - ASSERT_EQ(add(0, 0), 100000, "%d"); +TEST_CASE(queue_fill_to_capacity) { + int_queue q; + int_queue_init(&q); + + // Fill the queue to capacity (4 elements) + ASSERT_EQ(int_queue_push(&q, 1), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 2), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 3), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 4), 1, "%d"); + + // Try to push one more (should fail) + int result = int_queue_push(&q, 5); + ASSERT_EQ(result, 0, "%d"); + + return 0; +} + +TEST_CASE(queue_fifo_order) { + int_queue q; + int_queue_init(&q); + + // Push values in order + int_queue_push(&q, 10); + int_queue_push(&q, 20); + int_queue_push(&q, 30); + + // Pop values and check FIFO order + int val; + int_queue_pop(&q, &val); + ASSERT_EQ(val, 10, "%d"); + + int_queue_pop(&q, &val); + ASSERT_EQ(val, 20, "%d"); + + int_queue_pop(&q, &val); + ASSERT_EQ(val, 30, "%d"); + + return 0; +} + +TEST_CASE(queue_circular_behavior) { + int_queue q; + int_queue_init(&q); + + // Fill queue + int_queue_push(&q, 1); + int_queue_push(&q, 2); + int_queue_push(&q, 3); + int_queue_push(&q, 4); + + // Pop some elements + int val; + int_queue_pop(&q, &val); + int_queue_pop(&q, &val); + + // Should be able to push again + ASSERT_EQ(int_queue_push(&q, 5), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 6), 1, "%d"); + + // Verify order is maintained + int_queue_pop(&q, &val); + ASSERT_EQ(val, 3, "%d"); + int_queue_pop(&q, &val); + ASSERT_EQ(val, 4, "%d"); + int_queue_pop(&q, &val); + ASSERT_EQ(val, 5, "%d"); + int_queue_pop(&q, &val); + ASSERT_EQ(val, 6, "%d"); return 0; } |
