commit a54890e3f82bd8918da03e4af42fd88b2250db43
parent d87a7c2c90bfc67eae0cdd59614dbdca99b04200
Author: Remy Noulin <loader2x@gmail.com>
Date: Sat, 10 May 2025 18:25:14 +0200
fix segfault in expandHome when path is empty, add setValB to replace a btt with another one, add isEmptyB to check if a btt is empty, add isValidB to check if a btt has correct values, add eqCharB to compare a btt to a char, add joinCharB to join a vbtt list, add bCompactB to compact a vbtt list, add bNormalizePathB to normalize a btt holding a path
release/libsheepy.c | 24 ++++++
release/libsheepy.h | 2 +-
release/libsheepyBt.c | 213 +++++++++++++++++++++++++++++++++++++++++++++-
release/libsheepyBt.h | 56 ++++++++++--
release/libsheepyObject.h | 5 ++
src/libsheepy.c | 24 ++++++
src/libsheepy.h | 2 +-
src/libsheepyBt.c | 213 +++++++++++++++++++++++++++++++++++++++++++++-
src/libsheepyBt.h | 56 ++++++++++--
src/libsheepyObject.h | 5 ++
10 files changed, 586 insertions(+), 14 deletions(-)
Diffstat:
10 files changed, 586 insertions(+), 14 deletions(-)
diff --git a/release/libsheepy.c b/release/libsheepy.c
@@ -2465,6 +2465,12 @@ char *expandHome(const char* path) {
// duplicate path to be able to realloc (impossible when path is static)
p = strdup(path);
+ if (path[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(p);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(p, &exp_result, 0);;
@@ -2656,6 +2662,12 @@ char *iExpandHome(char **path) {
return(NULL);
}
+ if ((*path)[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(*path);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(*path, &exp_result, 0);;
@@ -2835,6 +2847,12 @@ char *bExpandHome(char *path) {
return(NULL);
}
+ if (path[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(path);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(path, &exp_result, 0);;
@@ -3015,6 +3033,12 @@ char *bLExpandHome(char *path, size_t pathSize) {
return(NULL);
}
+ if (path[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(path);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(path, &exp_result, 0);;
diff --git a/release/libsheepy.h b/release/libsheepy.h
@@ -98,7 +98,7 @@
// version accoring to the version package: Release.Major.minor.patch
// https://noulin.net/version/file/README.md.html
-#define LIBSHEEPY_VERSION "2.2.18.1"
+#define LIBSHEEPY_VERSION "2.2.19"
#ifndef SH_PREFIX
#define SH_PREFIX(NAME) NAME
diff --git a/release/libsheepyBt.c b/release/libsheepyBt.c
@@ -138,7 +138,7 @@ btt *allocPB(const btt *b) {
* return a btt object with a heap allocated copy of b.b
*/
btt copyB(const btt b) {
- btt r = b;
+ btt r = b;
r.b = malloc(b.len);
r.alloc = b.len ? b.len : 4 /*allocate 4 bytes when len is 0*/;
memcpy(r.b, b.b, b.len);
@@ -164,6 +164,13 @@ btt *dupPB(btt *b) {
ret dupB(*b);
}
+btt *setValB(btt *b, btt *a) {
+ if (!a or !b) ret null;
+ freenB(b);
+ *b = *a;
+ ret b;
+}
+
/**
* free heap allocated .b
* nothing if not heap allocated
@@ -239,6 +246,32 @@ char *toCharB(btt *b) {
ret r;
}
+bool isEmptyB(const btt b) {
+ if (not b.b or not b.len) ret yes;
+ ret no;
+}
+
+bool isEmptyPB(btt *b) {
+ if (not b) ret yes;
+ ret isEmptyB(*b);
+}
+
+bool isValidB(const btt b) {
+ if (not b.b or (b.alloc and b.len > b.alloc)) ret no;
+ ret yes;
+}
+
+bool isValidPB(btt *b) {
+ if (not b) ret no;
+ ret isValidB(*b);
+}
+
+bool eqCharB(btt a, char *b) {
+ if (not b or not a.b) ret no;
+ if (a.len != strlen(b)) ret no;
+ ret memcmp(a.b, b, a.len) == 0;
+}
+
/**
* return a btt object with s appended to b
* b doesn't need to have a heap allocated buffer
@@ -1124,6 +1157,39 @@ btt slicePB(const btt *b, int64_t start, int64_t end) {
ret sliceB(*b, start, end);
}
+/**
+ * join list, the elements are seperated with delim in the resulting string
+ *
+ * \param
+ * list
+ * \param
+ * delim: string seperator
+ * \return
+ * joined string (you must free the btt)
+ * empty when list or delim are NULL
+ */
+btt joinCharB(vbtt list, const char* delim) {
+ btt r = {0};
+
+ // sanity checks
+ if (!delim) {
+ return r;
+ }
+
+ vectorForEach(&list, e) {
+ if (!r.b) {
+ r = copyB(*e);
+ }
+ else {
+ // TODO check return value
+ bPushB(&r, delim);
+ bPushBB(&r, *e);
+ }
+ }
+ return(r);
+}
+
+
btt copyRngB(const btt b, int64_t start, int64_t end) {
btt s = sliceB(b, start, end);
if (not s.b) ret s; // error return {0}
@@ -1230,6 +1296,151 @@ btt trimPB(const btt *b) {
ret trimB(*b);
}
+/**
+ * remove empty strings from list
+ *
+ * \param
+ * list
+ * \return
+ * list without empty strings
+ * empty list when list is empty
+ * unchanged list when list is NULL
+ * NULL error
+ */
+vbtt *bCompactB(vbtt *list) {
+
+ // sanity checks
+ if (!list) {
+ return(NULL);
+ }
+
+ u16 rindex = 0;
+ // keep non empty elements
+ vectorForEach(list, e) {
+ trimBG(e);
+ if (not isEmptyPB(e)) {
+ list->array[rindex++] = *e;
+ }
+ else {
+ freenB(e);
+ }
+ }
+ list->count = rindex;
+ return(list);
+}
+
+/**
+ * buffer size normalize path
+ *
+ * remove unecessary /, .. and .
+ * leading / is kept
+ * leading .. is kept
+ * leading . is removed
+ *
+ * '/../' becomes '/'
+ *
+ * \param
+ * path
+ * \return
+ * path modified path
+ * NULL when path is NULL
+ */
+btt *bNormalizePathB(btt *path) {
+
+ // sanity checks
+ if (!path) {
+ return(NULL);
+ }
+
+ if (!path->len) {
+ return(path);
+ }
+
+ if (isEmptyPB(path)) {
+ return(path);
+ }
+
+ // list path elements
+ vbtt pathL = splitBG(path, "/");
+
+ // remove empty elements
+ bCompactB(&pathL);
+
+ if (pathL.count == 0) {
+ vectorFree(&pathL);
+ // keep leading /
+ path->b[0] = '/';
+ path->len = 1;
+ return(path);
+ }
+
+ // new path elements
+ vbtt list;
+ vectorInitCount(&list, pathL.count);
+
+ // detect leading double dots
+ bool onlyLeadingDoubleDots = true;
+
+ // add elements to list
+ vectorForEach(&pathL, level) {
+ if (eqCharB(*level, "..")) {
+ if (onlyLeadingDoubleDots) {
+ // keep leading ..
+ vectorAppend(&list, charB(strdup("..")));
+ }
+ else {
+ // remove .. in path
+ if (list.count) {
+ btt s = vectorPop(&list);
+ freeB(s);
+ }
+ }
+ }
+ else if (!eqCharB(*level, ".")) {
+ // remove . and add elements
+ btt c = copyB(*level);
+ vectorAppend(&list, c);
+ // an element is pushed, so this is the end of leading double dots
+ onlyLeadingDoubleDots = false;
+ }
+ }
+
+ if (list.count == 1 && eqCharB(vectorAt(&list, 0), "..") && path->b[0] == '/') {
+ // handle ../ .. /.. /../
+ vectorAt(&list, 0).len = 0;
+ }
+
+ // handle /.: add empty string
+ if (eqCharB(*path, "/.")) {
+ btt c = newB(4);
+ vectorAppend(&list, c);
+ }
+
+ // keep leading /
+ if (path->b[0] == '/') {
+ if (list.count == 0) {
+ // .. cancelled path: /a/b/../..
+ vectorFree(&pathL);
+ vectorFree(&list);
+ path->len = 1;
+ return(path);
+ }
+ //why? pErrorNULL(listPrependS(&list, ""));
+ }
+
+ // create new path
+ btt r = joinCharB(list, "/");
+ vectorFree(&pathL);
+ vectorFree(&list);
+ if (!r.b) {
+ path->len = 0;
+ return(path);
+ }
+ // TODO check null
+ setValB(path, &r);
+ return(path);
+}
+
void printB(const btt b) {
write(STDOUT_FILENO, b.b, b.len);
}
diff --git a/release/libsheepyBt.h b/release/libsheepyBt.h
@@ -65,7 +65,7 @@
*
* ```
* btt mystring = ccharB("Hello World!");
- * btt mystring2 = charB(strdup("Hello World!"), true);
+ * btt mystring2 = charB(strdup("Hello World!"));
* btt mybuf = voidB("Binary", sizeof("Binary"), false);
* createCharB(mystring3, "bytes", false);
* // from another string:
@@ -137,13 +137,13 @@
*
* ```
* btt ss = ccharB("Hello World!");
- * vbtt lsspl = splitB(ss, " ");
+ * vbtt ll = splitB(ss, " ");
* puts("");
* vectorForEach(&ll, e) {
* printB(*e);
* puts("");
* }
- * vectorFree(&lsspl);
+ * vectorFree(&ll);
* ```
*
*
@@ -186,7 +186,7 @@
* Empty btt:
* btt s = {0};
* Assign C string:
- * s = charB("a string", no);
+ * s = voidB("a string", no);
* s.b = "asdasd";
* b.len = strlen(s.b);
*
@@ -378,6 +378,9 @@ btt *dupPB(btt *b);
btt*: dupPB\
)(b)
+// assign a to b, b is freed if it was allocated
+btt *setValB(btt *b, btt *a);
+
// TODO:
// ..lenB
// ..freeB
@@ -396,7 +399,7 @@ btt *dupPB(btt *b);
// ..splitB
// splitLenB
// listFreeB
-// join
+// ..join
// ..toCharB convert to char*
// getB setB setBB set .b getBB get .b
//
@@ -451,6 +454,16 @@ void cleanUpFinishB(btt **val);
// convert btt to char* allocated on heap
char *toCharB(btt *b);
+bool isEmptyB(const btt b);
+
+bool isEmptyPB(btt *b);
+
+bool isValidB(const btt b);
+
+bool isValidPB(btt *b);
+
+bool eqCharB(btt a, char *b);
+
// return a btt object with s appended to b
// b doesn't need to have a heap allocated buffer
#define appendB pushB
@@ -837,6 +850,8 @@ btt slicePB(const btt *b, int64_t start, int64_t end);
const btt*: slicePB\
)(b, start, end);
+btt joinCharB(vbtt list, const char* delim);
+
// copyRngB
// return a btt object with a heap allocated copy of b.b from start to end
btt copyRngB(const btt b, int64_t start, int64_t end);
@@ -864,6 +879,37 @@ btt trimPB(const btt *b);
const btt*: trimPB\
)(b)
+/**
+ * remove empty strings from list
+ *
+ * \param
+ * list
+ * \return
+ * list without empty strings
+ * empty list when list is empty
+ * unchanged list when list is NULL
+ * NULL error
+ */
+vbtt *bCompactB(vbtt *list);
+
+/**
+ * buffer size normalize path
+ *
+ * remove unecessary /, .. and .
+ * leading / is kept
+ * leading .. is kept
+ * leading . is removed
+ *
+ * '/../' becomes '/'
+ *
+ * \param
+ * path
+ * \return
+ * path modified path
+ * NULL when path is NULL
+ */
+btt *bNormalizePathB(btt *path);
+
void printB(const btt b);
void printDebugB(const btt b);
diff --git a/release/libsheepyObject.h b/release/libsheepyObject.h
@@ -2895,6 +2895,11 @@ void finishManyOF(void *paramType, ...);
char ***: iListShiftNSmashS \
)(self, obj)
+// Delete an element in an array and move the elements after
+// to fill the gap (delElemG deletes the element and creates a gap)
+// the indices of the elements after are changed (-1)
+#define delElG(self, idx) delG(self, idx, (idx)+1)
+
#define delO(self, start, end) (self)->f->del(self, start, end)
#define delG(self, start, end) _Generic((self), \
smallDictt*: _Generic(start, \
diff --git a/src/libsheepy.c b/src/libsheepy.c
@@ -2521,6 +2521,12 @@ char *expandHome(const char* path) {
// duplicate path to be able to realloc (impossible when path is static)
p = strdup(path);
+ if (path[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(p);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(p, &exp_result, 0);;
@@ -2712,6 +2718,12 @@ char *iExpandHome(char **path) {
return(NULL);
}
+ if ((*path)[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(*path);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(*path, &exp_result, 0);;
@@ -2891,6 +2903,12 @@ char *bExpandHome(char *path) {
return(NULL);
}
+ if (path[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(path);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(path, &exp_result, 0);;
@@ -3071,6 +3089,12 @@ char *bLExpandHome(char *path, size_t pathSize) {
return(NULL);
}
+ if (path[0] == 0) {
+ // path is empty
+ // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults
+ return(path);
+ }
+
#if (!__TERMUX__)
// expand ~/
int status = wordexp(path, &exp_result, 0);;
diff --git a/src/libsheepy.h b/src/libsheepy.h
@@ -98,7 +98,7 @@
// version accoring to the version package: Release.Major.minor.patch
// https://noulin.net/version/file/README.md.html
-#define LIBSHEEPY_VERSION "2.2.18.1"
+#define LIBSHEEPY_VERSION "2.2.19"
#ifndef SH_PREFIX
#define SH_PREFIX(NAME) NAME
diff --git a/src/libsheepyBt.c b/src/libsheepyBt.c
@@ -138,7 +138,7 @@ btt *allocPB(const btt *b) {
* return a btt object with a heap allocated copy of b.b
*/
btt copyB(const btt b) {
- btt r = b;
+ btt r = b;
r.b = malloc(b.len);
r.alloc = b.len ? b.len : 4 /*allocate 4 bytes when len is 0*/;
memcpy(r.b, b.b, b.len);
@@ -164,6 +164,13 @@ btt *dupPB(btt *b) {
ret dupB(*b);
}
+btt *setValB(btt *b, btt *a) {
+ if (!a or !b) ret null;
+ freenB(b);
+ *b = *a;
+ ret b;
+}
+
/**
* free heap allocated .b
* nothing if not heap allocated
@@ -239,6 +246,32 @@ char *toCharB(btt *b) {
ret r;
}
+bool isEmptyB(const btt b) {
+ if (not b.b or not b.len) ret yes;
+ ret no;
+}
+
+bool isEmptyPB(btt *b) {
+ if (not b) ret yes;
+ ret isEmptyB(*b);
+}
+
+bool isValidB(const btt b) {
+ if (not b.b or (b.alloc and b.len > b.alloc)) ret no;
+ ret yes;
+}
+
+bool isValidPB(btt *b) {
+ if (not b) ret no;
+ ret isValidB(*b);
+}
+
+bool eqCharB(btt a, char *b) {
+ if (not b or not a.b) ret no;
+ if (a.len != strlen(b)) ret no;
+ ret memcmp(a.b, b, a.len) == 0;
+}
+
/**
* return a btt object with s appended to b
* b doesn't need to have a heap allocated buffer
@@ -1124,6 +1157,39 @@ btt slicePB(const btt *b, int64_t start, int64_t end) {
ret sliceB(*b, start, end);
}
+/**
+ * join list, the elements are seperated with delim in the resulting string
+ *
+ * \param
+ * list
+ * \param
+ * delim: string seperator
+ * \return
+ * joined string (you must free the btt)
+ * empty when list or delim are NULL
+ */
+btt joinCharB(vbtt list, const char* delim) {
+ btt r = {0};
+
+ // sanity checks
+ if (!delim) {
+ return r;
+ }
+
+ vectorForEach(&list, e) {
+ if (!r.b) {
+ r = copyB(*e);
+ }
+ else {
+ // TODO check return value
+ bPushB(&r, delim);
+ bPushBB(&r, *e);
+ }
+ }
+ return(r);
+}
+
+
btt copyRngB(const btt b, int64_t start, int64_t end) {
btt s = sliceB(b, start, end);
if (not s.b) ret s; // error return {0}
@@ -1230,6 +1296,151 @@ btt trimPB(const btt *b) {
ret trimB(*b);
}
+/**
+ * remove empty strings from list
+ *
+ * \param
+ * list
+ * \return
+ * list without empty strings
+ * empty list when list is empty
+ * unchanged list when list is NULL
+ * NULL error
+ */
+vbtt *bCompactB(vbtt *list) {
+
+ // sanity checks
+ if (!list) {
+ return(NULL);
+ }
+
+ u16 rindex = 0;
+ // keep non empty elements
+ vectorForEach(list, e) {
+ trimBG(e);
+ if (not isEmptyPB(e)) {
+ list->array[rindex++] = *e;
+ }
+ else {
+ freenB(e);
+ }
+ }
+ list->count = rindex;
+ return(list);
+}
+
+/**
+ * buffer size normalize path
+ *
+ * remove unecessary /, .. and .
+ * leading / is kept
+ * leading .. is kept
+ * leading . is removed
+ *
+ * '/../' becomes '/'
+ *
+ * \param
+ * path
+ * \return
+ * path modified path
+ * NULL when path is NULL
+ */
+btt *bNormalizePathB(btt *path) {
+
+ // sanity checks
+ if (!path) {
+ return(NULL);
+ }
+
+ if (!path->len) {
+ return(path);
+ }
+
+ if (isEmptyPB(path)) {
+ return(path);
+ }
+
+ // list path elements
+ vbtt pathL = splitBG(path, "/");
+
+ // remove empty elements
+ bCompactB(&pathL);
+
+ if (pathL.count == 0) {
+ vectorFree(&pathL);
+ // keep leading /
+ path->b[0] = '/';
+ path->len = 1;
+ return(path);
+ }
+
+ // new path elements
+ vbtt list;
+ vectorInitCount(&list, pathL.count);
+
+ // detect leading double dots
+ bool onlyLeadingDoubleDots = true;
+
+ // add elements to list
+ vectorForEach(&pathL, level) {
+ if (eqCharB(*level, "..")) {
+ if (onlyLeadingDoubleDots) {
+ // keep leading ..
+ vectorAppend(&list, charB(strdup("..")));
+ }
+ else {
+ // remove .. in path
+ if (list.count) {
+ btt s = vectorPop(&list);
+ freeB(s);
+ }
+ }
+ }
+ else if (!eqCharB(*level, ".")) {
+ // remove . and add elements
+ btt c = copyB(*level);
+ vectorAppend(&list, c);
+ // an element is pushed, so this is the end of leading double dots
+ onlyLeadingDoubleDots = false;
+ }
+ }
+
+ if (list.count == 1 && eqCharB(vectorAt(&list, 0), "..") && path->b[0] == '/') {
+ // handle ../ .. /.. /../
+ vectorAt(&list, 0).len = 0;
+ }
+
+ // handle /.: add empty string
+ if (eqCharB(*path, "/.")) {
+ btt c = newB(4);
+ vectorAppend(&list, c);
+ }
+
+ // keep leading /
+ if (path->b[0] == '/') {
+ if (list.count == 0) {
+ // .. cancelled path: /a/b/../..
+ vectorFree(&pathL);
+ vectorFree(&list);
+ path->len = 1;
+ return(path);
+ }
+ //why? pErrorNULL(listPrependS(&list, ""));
+ }
+
+ // create new path
+ btt r = joinCharB(list, "/");
+ vectorFree(&pathL);
+ vectorFree(&list);
+ if (!r.b) {
+ path->len = 0;
+ return(path);
+ }
+ // TODO check null
+ setValB(path, &r);
+ return(path);
+}
+
void printB(const btt b) {
write(STDOUT_FILENO, b.b, b.len);
}
diff --git a/src/libsheepyBt.h b/src/libsheepyBt.h
@@ -65,7 +65,7 @@
*
* ```
* btt mystring = ccharB("Hello World!");
- * btt mystring2 = charB(strdup("Hello World!"), true);
+ * btt mystring2 = charB(strdup("Hello World!"));
* btt mybuf = voidB("Binary", sizeof("Binary"), false);
* createCharB(mystring3, "bytes", false);
* // from another string:
@@ -137,13 +137,13 @@
*
* ```
* btt ss = ccharB("Hello World!");
- * vbtt lsspl = splitB(ss, " ");
+ * vbtt ll = splitB(ss, " ");
* puts("");
* vectorForEach(&ll, e) {
* printB(*e);
* puts("");
* }
- * vectorFree(&lsspl);
+ * vectorFree(&ll);
* ```
*
*
@@ -186,7 +186,7 @@
* Empty btt:
* btt s = {0};
* Assign C string:
- * s = charB("a string", no);
+ * s = voidB("a string", no);
* s.b = "asdasd";
* b.len = strlen(s.b);
*
@@ -378,6 +378,9 @@ btt *dupPB(btt *b);
btt*: dupPB\
)(b)
+// assign a to b, b is freed if it was allocated
+btt *setValB(btt *b, btt *a);
+
// TODO:
// ..lenB
// ..freeB
@@ -396,7 +399,7 @@ btt *dupPB(btt *b);
// ..splitB
// splitLenB
// listFreeB
-// join
+// ..join
// ..toCharB convert to char*
// getB setB setBB set .b getBB get .b
//
@@ -451,6 +454,16 @@ void cleanUpFinishB(btt **val);
// convert btt to char* allocated on heap
char *toCharB(btt *b);
+bool isEmptyB(const btt b);
+
+bool isEmptyPB(btt *b);
+
+bool isValidB(const btt b);
+
+bool isValidPB(btt *b);
+
+bool eqCharB(btt a, char *b);
+
// return a btt object with s appended to b
// b doesn't need to have a heap allocated buffer
#define appendB pushB
@@ -837,6 +850,8 @@ btt slicePB(const btt *b, int64_t start, int64_t end);
const btt*: slicePB\
)(b, start, end);
+btt joinCharB(vbtt list, const char* delim);
+
// copyRngB
// return a btt object with a heap allocated copy of b.b from start to end
btt copyRngB(const btt b, int64_t start, int64_t end);
@@ -864,6 +879,37 @@ btt trimPB(const btt *b);
const btt*: trimPB\
)(b)
+/**
+ * remove empty strings from list
+ *
+ * \param
+ * list
+ * \return
+ * list without empty strings
+ * empty list when list is empty
+ * unchanged list when list is NULL
+ * NULL error
+ */
+vbtt *bCompactB(vbtt *list);
+
+/**
+ * buffer size normalize path
+ *
+ * remove unecessary /, .. and .
+ * leading / is kept
+ * leading .. is kept
+ * leading . is removed
+ *
+ * '/../' becomes '/'
+ *
+ * \param
+ * path
+ * \return
+ * path modified path
+ * NULL when path is NULL
+ */
+btt *bNormalizePathB(btt *path);
+
void printB(const btt b);
void printDebugB(const btt b);
diff --git a/src/libsheepyObject.h b/src/libsheepyObject.h
@@ -2895,6 +2895,11 @@ void finishManyOF(void *paramType, ...);
char ***: iListShiftNSmashS \
)(self, obj)
+// Delete an element in an array and move the elements after
+// to fill the gap (delElemG deletes the element and creates a gap)
+// the indices of the elements after are changed (-1)
+#define delElG(self, idx) delG(self, idx, (idx)+1)
+
#define delO(self, start, end) (self)->f->del(self, start, end)
#define delG(self, start, end) _Generic((self), \
smallDictt*: _Generic(start, \