mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
320 lines
9.9 KiB
C++
320 lines
9.9 KiB
C++
// See the file "COPYING" in the main distribution directory for copyright.
|
|
|
|
#pragma once
|
|
|
|
// BaseList.h --
|
|
// Interface for class BaseList, current implementation is as an
|
|
// array of ent's. This implementation was chosen to optimize
|
|
// getting to the ent's rather than inserting and deleting.
|
|
// Also pairs of append's and get's act like push's and pop's
|
|
// and are very efficient. The only really expensive operations
|
|
// are inserting (but not appending), which requires pushing every
|
|
// element up, and resizing the list, which involves getting new space
|
|
// and moving the data. Resizing occurs automatically when inserting
|
|
// more elements than the list can currently hold. Automatic
|
|
// resizing is done by growing by GROWTH_FACTOR at a time and
|
|
// always increases the size of the list. Resizing to zero
|
|
// (or to less than the current value of num_entries)
|
|
// will decrease the size of the list to the current number of
|
|
// elements. Resize returns the new max_entries.
|
|
//
|
|
// Entries must be either a pointer to the data or nonzero data with
|
|
// sizeof(data) <= sizeof(void*).
|
|
|
|
#include <cassert>
|
|
#include <cstdarg>
|
|
#include <initializer_list>
|
|
#include <iterator>
|
|
|
|
#include "zeek/util.h"
|
|
|
|
namespace zeek {
|
|
|
|
enum class ListOrder : int { ORDERED, UNORDERED };
|
|
|
|
template<typename T, ListOrder Order = ListOrder::ORDERED>
|
|
class List {
|
|
public:
|
|
constexpr static int DEFAULT_LIST_SIZE = 10;
|
|
constexpr static int LIST_GROWTH_FACTOR = 2;
|
|
|
|
~List() { free(entries); }
|
|
explicit List(int size = 0) {
|
|
num_entries = 0;
|
|
|
|
if ( size <= 0 ) {
|
|
max_entries = 0;
|
|
entries = nullptr;
|
|
return;
|
|
}
|
|
|
|
max_entries = size;
|
|
|
|
entries = (T*)util::safe_malloc(max_entries * sizeof(T));
|
|
}
|
|
|
|
List(const List& b) {
|
|
max_entries = b.max_entries;
|
|
num_entries = b.num_entries;
|
|
|
|
if ( max_entries )
|
|
entries = (T*)util::safe_malloc(max_entries * sizeof(T));
|
|
else
|
|
entries = nullptr;
|
|
|
|
for ( int i = 0; i < num_entries; ++i )
|
|
entries[i] = b.entries[i];
|
|
}
|
|
|
|
List(List&& b) {
|
|
entries = b.entries;
|
|
num_entries = b.num_entries;
|
|
max_entries = b.max_entries;
|
|
|
|
b.entries = nullptr;
|
|
b.num_entries = b.max_entries = 0;
|
|
}
|
|
|
|
List(const T* arr, int n) {
|
|
num_entries = max_entries = n;
|
|
entries = (T*)util::safe_malloc(max_entries * sizeof(T));
|
|
memcpy(entries, arr, n * sizeof(T));
|
|
}
|
|
|
|
List(std::initializer_list<T> il) : List(il.begin(), il.size()) {}
|
|
|
|
List& operator=(const List& b) {
|
|
if ( this == &b )
|
|
return *this;
|
|
|
|
free(entries);
|
|
|
|
max_entries = b.max_entries;
|
|
num_entries = b.num_entries;
|
|
|
|
if ( max_entries )
|
|
entries = (T*)util::safe_malloc(max_entries * sizeof(T));
|
|
else
|
|
entries = nullptr;
|
|
|
|
for ( int i = 0; i < num_entries; ++i )
|
|
entries[i] = b.entries[i];
|
|
|
|
return *this;
|
|
}
|
|
|
|
List& operator=(List&& b) {
|
|
if ( this == &b )
|
|
return *this;
|
|
|
|
free(entries);
|
|
entries = b.entries;
|
|
num_entries = b.num_entries;
|
|
max_entries = b.max_entries;
|
|
|
|
b.entries = nullptr;
|
|
b.num_entries = b.max_entries = 0;
|
|
return *this;
|
|
}
|
|
|
|
// Return nth ent of list (do not remove).
|
|
T& operator[](int i) const { return entries[i]; }
|
|
|
|
void clear() // remove all entries
|
|
{
|
|
free(entries);
|
|
entries = nullptr;
|
|
num_entries = max_entries = 0;
|
|
}
|
|
|
|
bool empty() const noexcept { return num_entries == 0; }
|
|
size_t size() const noexcept { return num_entries; }
|
|
|
|
int length() const { return num_entries; }
|
|
int max() const { return max_entries; }
|
|
int resize(int new_size = 0) // 0 => size to fit current number of entries
|
|
{
|
|
if ( new_size < num_entries )
|
|
new_size = num_entries; // do not lose any entries
|
|
|
|
if ( new_size != max_entries ) {
|
|
entries = (T*)util::safe_realloc((void*)entries, sizeof(T) * new_size);
|
|
if ( entries )
|
|
max_entries = new_size;
|
|
else
|
|
max_entries = 0;
|
|
}
|
|
|
|
return max_entries;
|
|
}
|
|
|
|
void push_front(const T& a) {
|
|
if ( num_entries == max_entries )
|
|
resize(max_entries ? max_entries * LIST_GROWTH_FACTOR : DEFAULT_LIST_SIZE);
|
|
|
|
for ( int i = num_entries; i > 0; --i )
|
|
entries[i] = entries[i - 1]; // move all pointers up one
|
|
|
|
++num_entries;
|
|
entries[0] = a;
|
|
}
|
|
|
|
void push_back(const T& a) {
|
|
if ( num_entries == max_entries )
|
|
resize(max_entries ? max_entries * LIST_GROWTH_FACTOR : DEFAULT_LIST_SIZE);
|
|
|
|
entries[num_entries++] = a;
|
|
}
|
|
|
|
void pop_front() { remove_nth(0); }
|
|
void pop_back() { remove_nth(num_entries - 1); }
|
|
|
|
T& front() { return entries[0]; }
|
|
T& back() { return entries[num_entries - 1]; }
|
|
|
|
// The append method is maintained for historical/compatibility reasons.
|
|
// (It's commonly used in the event generation API)
|
|
void append(const T& a) // add to end of list
|
|
{
|
|
push_back(a);
|
|
}
|
|
|
|
bool remove(const T& a) // delete entry from list
|
|
{
|
|
int pos = member_pos(a);
|
|
if ( pos != -1 ) {
|
|
remove_nth(pos);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
T remove_nth(int n) // delete nth entry from list
|
|
{
|
|
assert(n >= 0 && n < num_entries);
|
|
|
|
T old_ent = entries[n];
|
|
|
|
// For data where we don't care about ordering, we don't care about keeping
|
|
// the list in the same order when removing an element. Just swap the last
|
|
// element with the element being removed.
|
|
if constexpr ( Order == ListOrder::ORDERED ) {
|
|
--num_entries;
|
|
|
|
for ( ; n < num_entries; ++n )
|
|
entries[n] = entries[n + 1];
|
|
}
|
|
else {
|
|
entries[n] = entries[num_entries - 1];
|
|
--num_entries;
|
|
}
|
|
|
|
return old_ent;
|
|
}
|
|
|
|
// Return 0 if ent is not in the list, ent otherwise.
|
|
bool is_member(const T& a) const {
|
|
int pos = member_pos(a);
|
|
return pos != -1;
|
|
}
|
|
|
|
// Returns -1 if ent is not in the list, otherwise its position.
|
|
int member_pos(const T& e) const {
|
|
int i;
|
|
for ( i = 0; i < length() && e != entries[i]; ++i )
|
|
;
|
|
|
|
return (i == length()) ? -1 : i;
|
|
}
|
|
|
|
T replace(int ent_index, const T& new_ent) // replace entry #i with a new value
|
|
{
|
|
if ( ent_index < 0 )
|
|
return T{};
|
|
|
|
T old_ent{};
|
|
|
|
if ( ent_index > num_entries - 1 ) { // replacement beyond the end of the list
|
|
resize(ent_index + 1);
|
|
|
|
for ( int i = num_entries; i < max_entries; ++i )
|
|
entries[i] = T{};
|
|
num_entries = max_entries;
|
|
}
|
|
else
|
|
old_ent = entries[ent_index];
|
|
|
|
entries[ent_index] = new_ent;
|
|
|
|
return old_ent;
|
|
}
|
|
|
|
// Type traits needed for some of the std algorithms to work
|
|
using value_type = T;
|
|
using pointer = T*;
|
|
using const_pointer = const T*;
|
|
|
|
// Iterator support
|
|
using iterator = pointer;
|
|
using const_iterator = const_pointer;
|
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
|
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
|
|
|
iterator begin() { return entries; }
|
|
iterator end() { return entries + num_entries; }
|
|
const_iterator begin() const { return entries; }
|
|
const_iterator end() const { return entries + num_entries; }
|
|
const_iterator cbegin() const { return entries; }
|
|
const_iterator cend() const { return entries + num_entries; }
|
|
|
|
reverse_iterator rbegin() { return reverse_iterator{end()}; }
|
|
reverse_iterator rend() { return reverse_iterator{begin()}; }
|
|
const_reverse_iterator rbegin() const { return const_reverse_iterator{end()}; }
|
|
const_reverse_iterator rend() const { return const_reverse_iterator{begin()}; }
|
|
const_reverse_iterator crbegin() const { return rbegin(); }
|
|
const_reverse_iterator crend() const { return rend(); }
|
|
|
|
protected:
|
|
// This could essentially be an std::vector if we wanted. Some
|
|
// reasons to maybe not refactor to use std::vector ?
|
|
//
|
|
// - Harder to use a custom growth factor. Also, the growth
|
|
// factor would be implementation-specific, taking some control over
|
|
// performance out of our hands.
|
|
//
|
|
// - It won't ever take advantage of realloc's occasional ability to
|
|
// grow in-place.
|
|
//
|
|
// - Combine above point this with lack of control of growth
|
|
// factor means the common choice of 2x growth factor causes
|
|
// a growth pattern that crawls forward in memory with no possible
|
|
// re-use of previous chunks (the new capacity is always larger than
|
|
// all previously allocated chunks combined). This point and
|
|
// whether 2x is empirically an issue still seems debated (at least
|
|
// GCC seems to stand by 2x as empirically better).
|
|
//
|
|
// - Sketchy shrinking behavior: standard says that requests to
|
|
// shrink are non-binding (it's expected implementations heed, but
|
|
// still not great to have no guarantee). Also, it would not take
|
|
// advantage of realloc's ability to contract in-place, it would
|
|
// allocate-and-copy.
|
|
|
|
T* entries;
|
|
int max_entries;
|
|
int num_entries;
|
|
};
|
|
|
|
// Specialization of the List class to store pointers of a type.
|
|
template<typename T, ListOrder Order = ListOrder::ORDERED>
|
|
using PList = List<T*, Order>;
|
|
|
|
// Popular type of list: list of strings.
|
|
using name_list = PList<char>;
|
|
|
|
} // namespace zeek
|
|
|
|
// Macro to visit each list element in turn.
|
|
#define loop_over_list(list, iterator) \
|
|
int iterator; \
|
|
for ( iterator = 0; iterator < (list).length(); ++iterator )
|