1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4#include <assert.h>
5#include <stddef.h>
6#include <string.h>
7
8#include "nvim/memory.h"
9#include "nvim/vim.h"
10#include "nvim/rbuffer.h"
11
12#ifdef INCLUDE_GENERATED_DECLARATIONS
13# include "rbuffer.c.generated.h"
14#endif
15
16/// Creates a new `RBuffer` instance.
17RBuffer *rbuffer_new(size_t capacity)
18 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
19{
20 if (!capacity) {
21 capacity = 0x10000;
22 }
23
24 RBuffer *rv = xcalloc(1, sizeof(RBuffer) + capacity);
25 rv->full_cb = rv->nonfull_cb = NULL;
26 rv->data = NULL;
27 rv->size = 0;
28 rv->write_ptr = rv->read_ptr = rv->start_ptr;
29 rv->end_ptr = rv->start_ptr + capacity;
30 rv->temp = NULL;
31 return rv;
32}
33
34void rbuffer_free(RBuffer *buf)
35{
36 xfree(buf->temp);
37 xfree(buf);
38}
39
40size_t rbuffer_size(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
41{
42 return buf->size;
43}
44
45size_t rbuffer_capacity(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
46{
47 return (size_t)(buf->end_ptr - buf->start_ptr);
48}
49
50size_t rbuffer_space(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
51{
52 return rbuffer_capacity(buf) - buf->size;
53}
54
55/// Return a pointer to a raw buffer containing the first empty slot available
56/// for writing. The second argument is a pointer to the maximum number of
57/// bytes that could be written.
58///
59/// It is necessary to call this function twice to ensure all empty space was
60/// used. See RBUFFER_UNTIL_FULL for a macro that simplifies this task.
61char *rbuffer_write_ptr(RBuffer *buf, size_t *write_count) FUNC_ATTR_NONNULL_ALL
62{
63 if (buf->size == rbuffer_capacity(buf)) {
64 *write_count = 0;
65 return NULL;
66 }
67
68 if (buf->write_ptr >= buf->read_ptr) {
69 *write_count = (size_t)(buf->end_ptr - buf->write_ptr);
70 } else {
71 *write_count = (size_t)(buf->read_ptr - buf->write_ptr);
72 }
73
74 return buf->write_ptr;
75}
76
77// Reset an RBuffer so read_ptr is at the beginning of the memory. If
78// necessary, this moves existing data by allocating temporary memory.
79void rbuffer_reset(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
80{
81 size_t temp_size;
82 if ((temp_size = rbuffer_size(buf))) {
83 if (buf->temp == NULL) {
84 buf->temp = xcalloc(1, rbuffer_capacity(buf));
85 }
86 rbuffer_read(buf, buf->temp, buf->size);
87 }
88 buf->read_ptr = buf->write_ptr = buf->start_ptr;
89 if (temp_size) {
90 rbuffer_write(buf, buf->temp, temp_size);
91 }
92}
93
94/// Adjust `rbuffer` write pointer to reflect produced data. This is called
95/// automatically by `rbuffer_write`, but when using `rbuffer_write_ptr`
96/// directly, this needs to called after the data was copied to the internal
97/// buffer. The write pointer will be wrapped if required.
98void rbuffer_produced(RBuffer *buf, size_t count) FUNC_ATTR_NONNULL_ALL
99{
100 assert(count && count <= rbuffer_space(buf));
101
102 buf->write_ptr += count;
103 if (buf->write_ptr >= buf->end_ptr) {
104 // wrap around
105 buf->write_ptr -= rbuffer_capacity(buf);
106 }
107
108 buf->size += count;
109 if (buf->full_cb && !rbuffer_space(buf)) {
110 buf->full_cb(buf, buf->data);
111 }
112}
113
114/// Return a pointer to a raw buffer containing the first byte available
115/// for reading. The second argument is a pointer to the maximum number of
116/// bytes that could be read.
117///
118/// It is necessary to call this function twice to ensure all available bytes
119/// were read. See RBUFFER_UNTIL_EMPTY for a macro that simplifies this task.
120char *rbuffer_read_ptr(RBuffer *buf, size_t *read_count) FUNC_ATTR_NONNULL_ALL
121{
122 if (!buf->size) {
123 *read_count = 0;
124 return buf->read_ptr;
125 }
126
127 if (buf->read_ptr < buf->write_ptr) {
128 *read_count = (size_t)(buf->write_ptr - buf->read_ptr);
129 } else {
130 *read_count = (size_t)(buf->end_ptr - buf->read_ptr);
131 }
132
133 return buf->read_ptr;
134}
135
136/// Adjust `rbuffer` read pointer to reflect consumed data. This is called
137/// automatically by `rbuffer_read`, but when using `rbuffer_read_ptr`
138/// directly, this needs to called after the data was copied from the internal
139/// buffer. The read pointer will be wrapped if required.
140void rbuffer_consumed(RBuffer *buf, size_t count)
141 FUNC_ATTR_NONNULL_ALL
142{
143 assert(count && count <= buf->size);
144
145 buf->read_ptr += count;
146 if (buf->read_ptr >= buf->end_ptr) {
147 buf->read_ptr -= rbuffer_capacity(buf);
148 }
149
150 bool was_full = buf->size == rbuffer_capacity(buf);
151 buf->size -= count;
152 if (buf->nonfull_cb && was_full) {
153 buf->nonfull_cb(buf, buf->data);
154 }
155}
156
157// Higher level functions for copying from/to RBuffer instances and data
158// pointers
159size_t rbuffer_write(RBuffer *buf, const char *src, size_t src_size)
160 FUNC_ATTR_NONNULL_ALL
161{
162 size_t size = src_size;
163
164 RBUFFER_UNTIL_FULL(buf, wptr, wcnt) {
165 size_t copy_count = MIN(src_size, wcnt);
166 memcpy(wptr, src, copy_count);
167 rbuffer_produced(buf, copy_count);
168
169 if (!(src_size -= copy_count)) {
170 return size;
171 }
172
173 src += copy_count;
174 }
175
176 return size - src_size;
177}
178
179size_t rbuffer_read(RBuffer *buf, char *dst, size_t dst_size)
180 FUNC_ATTR_NONNULL_ALL
181{
182 size_t size = dst_size;
183
184 RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) {
185 size_t copy_count = MIN(dst_size, rcnt);
186 memcpy(dst, rptr, copy_count);
187 rbuffer_consumed(buf, copy_count);
188
189 if (!(dst_size -= copy_count)) {
190 return size;
191 }
192
193 dst += copy_count;
194 }
195
196 return size - dst_size;
197}
198
199char *rbuffer_get(RBuffer *buf, size_t index)
200 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
201{
202 assert(index < buf->size);
203 char *rptr = buf->read_ptr + index;
204 if (rptr >= buf->end_ptr) {
205 rptr -= rbuffer_capacity(buf);
206 }
207 return rptr;
208}
209
210int rbuffer_cmp(RBuffer *buf, const char *str, size_t count)
211 FUNC_ATTR_NONNULL_ALL
212{
213 assert(count <= buf->size);
214 size_t rcnt;
215 (void)rbuffer_read_ptr(buf, &rcnt);
216 size_t n = MIN(count, rcnt);
217 int rv = memcmp(str, buf->read_ptr, n);
218 count -= n;
219 size_t remaining = buf->size - rcnt;
220
221 if (rv || !count || !remaining) {
222 return rv;
223 }
224
225 return memcmp(str + n, buf->start_ptr, count);
226}
227
228