Changeset View
Changeset View
Standalone View
Standalone View
sys/sys/tree.h
Show First 20 Lines • Show All 350 Lines • ▼ Show 20 Lines | _RB_BITSUP(dst, field) = (__uintptr_t)src | \ | ||||
(_RB_BITSUP(dst, field) & _RB_LR); \ | (_RB_BITSUP(dst, field) & _RB_LR); \ | ||||
} while (/*CONSTCOND*/ 0) | } while (/*CONSTCOND*/ 0) | ||||
#define RB_SET(elm, parent, field) do { \ | #define RB_SET(elm, parent, field) do { \ | ||||
_RB_UP(elm, field) = parent; \ | _RB_UP(elm, field) = parent; \ | ||||
RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ | RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ | ||||
} while (/*CONSTCOND*/ 0) | } while (/*CONSTCOND*/ 0) | ||||
alc: Why add a blank line here? | |||||
Done Inline ActionsNo good reason. dougm: No good reason. | |||||
/* | /* | ||||
* Something to be invoked in a loop at the root of every modified subtree, | * Either RB_AUGMENT or RB_AUGMENT_CHECK is invoked in a loop at the root of | ||||
* from the bottom up to the root, to update augmented node data. | * every modified subtree, from the bottom up to the root, to update augmented | ||||
* node data. RB_AUGMENT_CHECK returns true only when the update changes the | |||||
* node data, so that updating can be stopped short of the root when it returns | |||||
* false. | |||||
*/ | */ | ||||
#ifndef RB_AUGMENT_CHECK | |||||
#ifndef RB_AUGMENT | #ifndef RB_AUGMENT | ||||
#define RB_AUGMENT(x) break | #define RB_AUGMENT_CHECK(x) false | ||||
#else | |||||
#define RB_AUGMENT_CHECK(x) (RB_AUGMENT(x), true) | |||||
#endif | #endif | ||||
#endif | |||||
Not Done Inline ActionsShould we have a safety belt to ensure that both RB_AUGMENT_CHECK(x) and RB_AUGMENT(x) are not defined? Should the man page explicitly forbid both being defined? alc: Should we have a safety belt to ensure that both RB_AUGMENT_CHECK(x) and RB_AUGMENT(x) are not… | |||||
Done Inline ActionsI don't mind adding something, but if someone defines correct versions of both, the code will correctly use one and ignore the other. So no harm will come to them. dougm: I don't mind adding something, but if someone defines correct versions of both, the code will… | |||||
#define RB_UPDATE_AUGMENT(elm, field) do { \ | #define RB_UPDATE_AUGMENT(elm, field) do { \ | ||||
__typeof(elm) rb_update_tmp = (elm); \ | __typeof(elm) rb_update_tmp = (elm); \ | ||||
do { \ | while (RB_AUGMENT_CHECK(rb_update_tmp) && \ | ||||
RB_AUGMENT(rb_update_tmp); \ | (rb_update_tmp = RB_PARENT(rb_update_tmp, field)) != NULL); \ | ||||
Not Done Inline ActionsI would put the ; on the next line, so that it is more obvious that the loop body is intentionally empty. alc: I would put the ; on the next line, so that it is more obvious that the loop body is… | |||||
} while ((rb_update_tmp = RB_PARENT(rb_update_tmp, field)) != NULL); \ | |||||
} while (0) | } while (0) | ||||
#define RB_SWAP_CHILD(head, par, out, in, field) do { \ | #define RB_SWAP_CHILD(head, par, out, in, field) do { \ | ||||
if (par == NULL) \ | if (par == NULL) \ | ||||
RB_ROOT(head) = (in); \ | RB_ROOT(head) = (in); \ | ||||
else if ((out) == RB_LEFT(par, field)) \ | else if ((out) == RB_LEFT(par, field)) \ | ||||
RB_LEFT(par, field) = (in); \ | RB_LEFT(par, field) = (in); \ | ||||
else \ | else \ | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | #define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ | ||||
RB_PROTOTYPE_REINSERT(name, type, attr); | RB_PROTOTYPE_REINSERT(name, type, attr); | ||||
#ifdef _RB_DIAGNOSTIC | #ifdef _RB_DIAGNOSTIC | ||||
#define RB_PROTOTYPE_RANK(name, type, attr) \ | #define RB_PROTOTYPE_RANK(name, type, attr) \ | ||||
attr int name##_RB_RANK(struct type *); | attr int name##_RB_RANK(struct type *); | ||||
#else | #else | ||||
#define RB_PROTOTYPE_RANK(name, type, attr) | #define RB_PROTOTYPE_RANK(name, type, attr) | ||||
#endif | #endif | ||||
#define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \ | #define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \ | ||||
attr void name##_RB_INSERT_COLOR(struct name *, \ | attr struct type *name##_RB_INSERT_COLOR(struct name *, \ | ||||
struct type *, struct type *) | struct type *, struct type *) | ||||
#define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \ | #define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \ | ||||
attr void name##_RB_REMOVE_COLOR(struct name *, \ | attr struct type *name##_RB_REMOVE_COLOR(struct name *, \ | ||||
struct type *, struct type *) | struct type *, struct type *) | ||||
#define RB_PROTOTYPE_REMOVE(name, type, attr) \ | #define RB_PROTOTYPE_REMOVE(name, type, attr) \ | ||||
attr struct type *name##_RB_REMOVE(struct name *, struct type *) | attr struct type *name##_RB_REMOVE(struct name *, struct type *) | ||||
#define RB_PROTOTYPE_INSERT(name, type, attr) \ | #define RB_PROTOTYPE_INSERT(name, type, attr) \ | ||||
attr struct type *name##_RB_INSERT(struct name *, struct type *) | attr struct type *name##_RB_INSERT(struct name *, struct type *) | ||||
#define RB_PROTOTYPE_FIND(name, type, attr) \ | #define RB_PROTOTYPE_FIND(name, type, attr) \ | ||||
attr struct type *name##_RB_FIND(struct name *, struct type *) | attr struct type *name##_RB_FIND(struct name *, struct type *) | ||||
#define RB_PROTOTYPE_NFIND(name, type, attr) \ | #define RB_PROTOTYPE_NFIND(name, type, attr) \ | ||||
Show All 23 Lines | #define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ | ||||
RB_GENERATE_FIND(name, type, field, cmp, attr) \ | RB_GENERATE_FIND(name, type, field, cmp, attr) \ | ||||
RB_GENERATE_NFIND(name, type, field, cmp, attr) \ | RB_GENERATE_NFIND(name, type, field, cmp, attr) \ | ||||
RB_GENERATE_NEXT(name, type, field, attr) \ | RB_GENERATE_NEXT(name, type, field, attr) \ | ||||
RB_GENERATE_PREV(name, type, field, attr) \ | RB_GENERATE_PREV(name, type, field, attr) \ | ||||
RB_GENERATE_MINMAX(name, type, field, attr) \ | RB_GENERATE_MINMAX(name, type, field, attr) \ | ||||
RB_GENERATE_REINSERT(name, type, field, cmp, attr) | RB_GENERATE_REINSERT(name, type, field, cmp, attr) | ||||
#ifdef _RB_DIAGNOSTIC | #ifdef _RB_DIAGNOSTIC | ||||
#ifndef RB_AUGMENT | |||||
#define _RB_AUGMENT_VERIFY(x) RB_AUGMENT_CHECK(x) | |||||
#else | |||||
#define _RB_AUGMENT_VERIFY(x) false | |||||
#endif | |||||
#define RB_GENERATE_RANK(name, type, field, attr) \ | #define RB_GENERATE_RANK(name, type, field, attr) \ | ||||
/* \ | |||||
* Return the rank of the subtree rooted at elm, or -1 if the subtree \ | |||||
* is not rank-balanced, or has inconsistent augmentation data. | |||||
*/ \ | |||||
attr int \ | attr int \ | ||||
name##_RB_RANK(struct type *elm) \ | name##_RB_RANK(struct type *elm) \ | ||||
{ \ | { \ | ||||
struct type *left, *right, *up; \ | struct type *left, *right, *up; \ | ||||
int left_rank, right_rank; \ | int left_rank, right_rank; \ | ||||
\ | \ | ||||
if (elm == NULL) \ | if (elm == NULL) \ | ||||
return (0); \ | return (0); \ | ||||
up = _RB_UP(elm, field); \ | up = _RB_UP(elm, field); \ | ||||
left = RB_LEFT(elm, field); \ | left = RB_LEFT(elm, field); \ | ||||
left_rank = ((_RB_BITS(up) & _RB_L) ? 2 : 1) + \ | left_rank = ((_RB_BITS(up) & _RB_L) ? 2 : 1) + \ | ||||
name##_RB_RANK(left); \ | name##_RB_RANK(left); \ | ||||
right = RB_RIGHT(elm, field); \ | right = RB_RIGHT(elm, field); \ | ||||
right_rank = ((_RB_BITS(up) & _RB_R) ? 2 : 1) + \ | right_rank = ((_RB_BITS(up) & _RB_R) ? 2 : 1) + \ | ||||
name##_RB_RANK(right); \ | name##_RB_RANK(right); \ | ||||
if (left_rank != right_rank || \ | if (left_rank != right_rank || \ | ||||
(left_rank == 2 && left == NULL && right == NULL)) \ | (left_rank == 2 && left == NULL && right == NULL) || \ | ||||
_RB_AUGMENT_VERIFY(elm)) \ | |||||
return (-1); \ | return (-1); \ | ||||
return (left_rank); \ | return (left_rank); \ | ||||
} | } | ||||
#else | #else | ||||
#define RB_GENERATE_RANK(name, type, field, attr) | #define RB_GENERATE_RANK(name, type, field, attr) | ||||
#endif | #endif | ||||
#define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ | #define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ | ||||
attr void \ | attr struct type * \ | ||||
name##_RB_INSERT_COLOR(struct name *head, \ | name##_RB_INSERT_COLOR(struct name *head, \ | ||||
struct type *parent, struct type *elm) \ | struct type *parent, struct type *elm) \ | ||||
{ \ | { \ | ||||
/* \ | /* \ | ||||
* Initially, elm is a leaf. Either its parent was previously \ | * Initially, elm is a leaf. Either its parent was previously \ | ||||
* a leaf, with two black null children, or an interior node \ | * a leaf, with two black null children, or an interior node \ | ||||
* with a black non-null child and a red null child. The \ | * with a black non-null child and a red null child. The \ | ||||
* balance criterion "the rank of any leaf is 1" precludes the \ | * balance criterion "the rank of any leaf is 1" precludes the \ | ||||
* possibility of two red null children for the initial parent. \ | * possibility of two red null children for the initial parent. \ | ||||
* So the first loop iteration cannot lead to accessing an \ | * So the first loop iteration cannot lead to accessing an \ | ||||
* uninitialized 'child', and a later iteration can only happen \ | * uninitialized 'child', and a later iteration can only happen \ | ||||
* when a value has been assigned to 'child' in the previous \ | * when a value has been assigned to 'child' in the previous \ | ||||
* one. \ | * one. \ | ||||
*/ \ | */ \ | ||||
struct type *child, *child_up, *gpar; \ | struct type *child, *child_up, *gpar; \ | ||||
__uintptr_t elmdir, sibdir; \ | __uintptr_t elmdir, sibdir; \ | ||||
\ | \ | ||||
do { \ | do { \ | ||||
/* the rank of the tree rooted at elm grew */ \ | /* the rank of the tree rooted at elm grew */ \ | ||||
gpar = _RB_UP(parent, field); \ | gpar = _RB_UP(parent, field); \ | ||||
elmdir = RB_RIGHT(parent, field) == elm ? _RB_R : _RB_L; \ | elmdir = RB_RIGHT(parent, field) == elm ? _RB_R : _RB_L; \ | ||||
if (_RB_BITS(gpar) & elmdir) { \ | if (_RB_BITS(gpar) & elmdir) { \ | ||||
/* shorten the parent-elm edge to rebalance */ \ | /* shorten the parent-elm edge to rebalance */ \ | ||||
_RB_BITSUP(parent, field) ^= elmdir; \ | _RB_BITSUP(parent, field) ^= elmdir; \ | ||||
return; \ | return (NULL); \ | ||||
} \ | } \ | ||||
sibdir = elmdir ^ _RB_LR; \ | sibdir = elmdir ^ _RB_LR; \ | ||||
/* the other edge must change length */ \ | /* the other edge must change length */ \ | ||||
_RB_BITSUP(parent, field) ^= sibdir; \ | _RB_BITSUP(parent, field) ^= sibdir; \ | ||||
if ((_RB_BITS(gpar) & _RB_LR) == 0) { \ | if ((_RB_BITS(gpar) & _RB_LR) == 0) { \ | ||||
/* both edges now short, retry from parent */ \ | /* both edges now short, retry from parent */ \ | ||||
child = elm; \ | child = elm; \ | ||||
elm = parent; \ | elm = parent; \ | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | do { \ | ||||
* / \ / z \ | * / \ / z \ | ||||
* x \ y \ | * x \ y \ | ||||
* y \ | * y \ | ||||
*/ \ | */ \ | ||||
RB_ROTATE(parent, child, sibdir, field); \ | RB_ROTATE(parent, child, sibdir, field); \ | ||||
_RB_UP(child, field) = gpar; \ | _RB_UP(child, field) = gpar; \ | ||||
RB_SWAP_CHILD(head, gpar, parent, child, field); \ | RB_SWAP_CHILD(head, gpar, parent, child, field); \ | ||||
if (elm != child) \ | if (elm != child) \ | ||||
RB_AUGMENT(elm); \ | RB_AUGMENT_CHECK(elm); \ | ||||
RB_AUGMENT(parent); \ | RB_AUGMENT_CHECK(parent); \ | ||||
break; \ | return (child); \ | ||||
} while ((parent = gpar) != NULL); \ | } while ((parent = gpar) != NULL); \ | ||||
return (NULL); \ | |||||
} | } | ||||
#ifndef RB_STRICT_HST | #ifndef RB_STRICT_HST | ||||
/* | /* | ||||
* In REMOVE_COLOR, the HST paper, in figure 3, in the single-rotate case, has | * In REMOVE_COLOR, the HST paper, in figure 3, in the single-rotate case, has | ||||
* 'parent' with one higher rank, and then reduces its rank if 'parent' has | * 'parent' with one higher rank, and then reduces its rank if 'parent' has | ||||
* become a leaf. This implementation always has the parent in its new position | * become a leaf. This implementation always has the parent in its new position | ||||
* with lower rank, to avoid the leaf check. Define RB_STRICT_HST to 1 to get | * with lower rank, to avoid the leaf check. Define RB_STRICT_HST to 1 to get | ||||
* the behavior that HST describes. | * the behavior that HST describes. | ||||
*/ | */ | ||||
#define RB_STRICT_HST 0 | #define RB_STRICT_HST 0 | ||||
#endif | #endif | ||||
#define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ | #define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ | ||||
attr void \ | attr struct type * \ | ||||
name##_RB_REMOVE_COLOR(struct name *head, \ | name##_RB_REMOVE_COLOR(struct name *head, \ | ||||
struct type *parent, struct type *elm) \ | struct type *parent, struct type *elm) \ | ||||
{ \ | { \ | ||||
struct type *gpar, *sib, *up; \ | struct type *gpar, *sib, *up; \ | ||||
__uintptr_t elmdir, sibdir; \ | __uintptr_t elmdir, sibdir; \ | ||||
\ | \ | ||||
if (RB_RIGHT(parent, field) == elm && \ | if (RB_RIGHT(parent, field) == elm && \ | ||||
RB_LEFT(parent, field) == elm) { \ | RB_LEFT(parent, field) == elm) { \ | ||||
/* Deleting a leaf that is an only-child creates a \ | /* Deleting a leaf that is an only-child creates a \ | ||||
* rank-2 leaf. Demote that leaf. */ \ | * rank-2 leaf. Demote that leaf. */ \ | ||||
_RB_UP(parent, field) = _RB_PTR(_RB_UP(parent, field)); \ | _RB_UP(parent, field) = _RB_PTR(_RB_UP(parent, field)); \ | ||||
elm = parent; \ | elm = parent; \ | ||||
if ((parent = _RB_UP(elm, field)) == NULL) \ | if ((parent = _RB_UP(elm, field)) == NULL) \ | ||||
return; \ | return (NULL); \ | ||||
} \ | } \ | ||||
do { \ | do { \ | ||||
/* the rank of the tree rooted at elm shrank */ \ | /* the rank of the tree rooted at elm shrank */ \ | ||||
gpar = _RB_UP(parent, field); \ | gpar = _RB_UP(parent, field); \ | ||||
elmdir = RB_RIGHT(parent, field) == elm ? _RB_R : _RB_L; \ | elmdir = RB_RIGHT(parent, field) == elm ? _RB_R : _RB_L; \ | ||||
_RB_BITS(gpar) ^= elmdir; \ | _RB_BITS(gpar) ^= elmdir; \ | ||||
if (_RB_BITS(gpar) & elmdir) { \ | if (_RB_BITS(gpar) & elmdir) { \ | ||||
/* lengthen the parent-elm edge to rebalance */ \ | /* lengthen the parent-elm edge to rebalance */ \ | ||||
_RB_UP(parent, field) = gpar; \ | _RB_UP(parent, field) = gpar; \ | ||||
return; \ | return (NULL); \ | ||||
} \ | } \ | ||||
if (_RB_BITS(gpar) & _RB_LR) { \ | if (_RB_BITS(gpar) & _RB_LR) { \ | ||||
/* shorten other edge, retry from parent */ \ | /* shorten other edge, retry from parent */ \ | ||||
_RB_BITS(gpar) ^= _RB_LR; \ | _RB_BITS(gpar) ^= _RB_LR; \ | ||||
_RB_UP(parent, field) = gpar; \ | _RB_UP(parent, field) = gpar; \ | ||||
gpar = _RB_PTR(gpar); \ | gpar = _RB_PTR(gpar); \ | ||||
continue; \ | continue; \ | ||||
} \ | } \ | ||||
▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | do { \ | ||||
* / \ / \ \ | * / \ / \ \ | ||||
* / \ e \ \ | * / \ e \ \ | ||||
* x s x \ | * x s x \ | ||||
*/ \ | */ \ | ||||
RB_ROTATE(parent, elm, elmdir, field); \ | RB_ROTATE(parent, elm, elmdir, field); \ | ||||
RB_SET_PARENT(elm, gpar, field); \ | RB_SET_PARENT(elm, gpar, field); \ | ||||
RB_SWAP_CHILD(head, gpar, parent, elm, field); \ | RB_SWAP_CHILD(head, gpar, parent, elm, field); \ | ||||
if (sib != elm) \ | if (sib != elm) \ | ||||
RB_AUGMENT(sib); \ | RB_AUGMENT_CHECK(sib); \ | ||||
break; \ | return (parent); \ | ||||
} while (elm = parent, (parent = gpar) != NULL); \ | } while (elm = parent, (parent = gpar) != NULL); \ | ||||
return (NULL); \ | |||||
} | } | ||||
#define _RB_AUGMENT_WALK(elm, match, field) \ | |||||
do { \ | |||||
if (match == elm) \ | |||||
match = NULL; \ | |||||
} while (RB_AUGMENT_CHECK(elm) && \ | |||||
(elm = RB_PARENT(elm, field)) != NULL) | |||||
#define RB_GENERATE_REMOVE(name, type, field, attr) \ | #define RB_GENERATE_REMOVE(name, type, field, attr) \ | ||||
attr struct type * \ | attr struct type * \ | ||||
name##_RB_REMOVE(struct name *head, struct type *out) \ | name##_RB_REMOVE(struct name *head, struct type *out) \ | ||||
{ \ | { \ | ||||
struct type *child, *in, *opar, *parent; \ | struct type *child, *in, *opar, *parent; \ | ||||
\ | \ | ||||
child = RB_LEFT(out, field); \ | child = RB_LEFT(out, field); \ | ||||
in = RB_RIGHT(out, field); \ | in = RB_RIGHT(out, field); \ | ||||
Show All 16 Lines | if (in == NULL || child == NULL) { \ | ||||
} \ | } \ | ||||
_RB_UP(in, field) = opar; \ | _RB_UP(in, field) = opar; \ | ||||
opar = _RB_PTR(opar); \ | opar = _RB_PTR(opar); \ | ||||
} \ | } \ | ||||
RB_SWAP_CHILD(head, opar, out, in, field); \ | RB_SWAP_CHILD(head, opar, out, in, field); \ | ||||
if (child != NULL) \ | if (child != NULL) \ | ||||
_RB_UP(child, field) = parent; \ | _RB_UP(child, field) = parent; \ | ||||
if (parent != NULL) { \ | if (parent != NULL) { \ | ||||
name##_RB_REMOVE_COLOR(head, parent, child); \ | opar = name##_RB_REMOVE_COLOR(head, parent, child); \ | ||||
/* if rotation has made 'parent' the root of the same \ | /* if rotation has made 'parent' the root of the same \ | ||||
* subtree as before, don't re-augment it. */ \ | * subtree as before, don't re-augment it. */ \ | ||||
if (parent == in && RB_LEFT(parent, field) == NULL) \ | if (parent == in && RB_LEFT(parent, field) == NULL) { \ | ||||
opar = NULL; \ | |||||
parent = RB_PARENT(parent, field); \ | parent = RB_PARENT(parent, field); \ | ||||
RB_UPDATE_AUGMENT(parent, field); \ | |||||
} \ | } \ | ||||
_RB_AUGMENT_WALK(parent, opar, field); \ | |||||
if (opar != NULL) { \ | |||||
RB_AUGMENT_CHECK(opar); \ | |||||
RB_AUGMENT_CHECK(RB_PARENT(opar, field)); \ | |||||
} \ | |||||
} \ | |||||
return (out); \ | return (out); \ | ||||
} | } | ||||
#define RB_GENERATE_INSERT(name, type, field, cmp, attr) \ | #define RB_GENERATE_INSERT(name, type, field, cmp, attr) \ | ||||
/* Inserts a node into the RB tree */ \ | /* Inserts a node into the RB tree */ \ | ||||
attr struct type * \ | attr struct type * \ | ||||
name##_RB_INSERT(struct name *head, struct type *elm) \ | name##_RB_INSERT(struct name *head, struct type *elm) \ | ||||
{ \ | { \ | ||||
Show All 9 Lines | while ((tmp = *tmpp) != NULL) { \ | ||||
else if (comp > 0) \ | else if (comp > 0) \ | ||||
tmpp = &RB_RIGHT(parent, field); \ | tmpp = &RB_RIGHT(parent, field); \ | ||||
else \ | else \ | ||||
return (parent); \ | return (parent); \ | ||||
} \ | } \ | ||||
RB_SET(elm, parent, field); \ | RB_SET(elm, parent, field); \ | ||||
*tmpp = elm; \ | *tmpp = elm; \ | ||||
if (parent != NULL) \ | if (parent != NULL) \ | ||||
name##_RB_INSERT_COLOR(head, parent, elm); \ | tmp = name##_RB_INSERT_COLOR(head, parent, elm); \ | ||||
RB_UPDATE_AUGMENT(elm, field); \ | _RB_AUGMENT_WALK(elm, tmp, field); \ | ||||
if (tmp != NULL) \ | |||||
RB_AUGMENT_CHECK(tmp); \ | |||||
return (NULL); \ | return (NULL); \ | ||||
} | } | ||||
#define RB_GENERATE_FIND(name, type, field, cmp, attr) \ | #define RB_GENERATE_FIND(name, type, field, cmp, attr) \ | ||||
/* Finds the node with the same key as elm */ \ | /* Finds the node with the same key as elm */ \ | ||||
attr struct type * \ | attr struct type * \ | ||||
name##_RB_FIND(struct name *head, struct type *elm) \ | name##_RB_FIND(struct name *head, struct type *elm) \ | ||||
{ \ | { \ | ||||
▲ Show 20 Lines • Show All 148 Lines • Show Last 20 Lines |
Why add a blank line here?