Initial commit

This commit is contained in:
2025-11-27 16:42:36 +00:00
commit a95e5c09b8
7 changed files with 2188 additions and 0 deletions

853
src/rope.cpp Normal file
View File

@@ -0,0 +1,853 @@
#include "../headers/rope.hpp"
#include "../headers/noose.hpp"
#include <assert.h>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <pcre2.h>
#include <stdio.h>
#include <string.h>
static void update(Knot *n) {
if (!n)
return;
if (!n->depth || n->depth == 0)
return;
uint32_t left_chars = n->left ? n->left->char_count : 0;
uint32_t right_chars = n->right ? n->right->char_count : 0;
n->char_count = left_chars + right_chars;
uint32_t left_lines = n->left ? n->left->line_count : 0;
uint32_t right_lines = n->right ? n->right->line_count : 0;
n->line_count = left_lines + right_lines;
uint8_t left_depth = n->left ? n->left->depth : 0;
uint8_t right_depth = n->right ? n->right->depth : 0;
n->depth = MAX(left_depth, right_depth) + 1;
n->chunk_size = n->left ? n->left->chunk_size : n->right->chunk_size;
}
// str is not consumed and \0 is not handled
// So if str is null terminated then len must be strlen(str)
// and freed by caller
Knot *load(char *str, uint32_t len, uint32_t chunk_size) {
if (len > (uint32_t)(chunk_size - (chunk_size / 16))) {
Knot *left = load(str, len / 2, chunk_size);
Knot *right = load(str + len / 2, len - len / 2, chunk_size);
Knot *node = (Knot *)malloc(sizeof(Knot));
if (!node)
return nullptr;
node->left = left;
node->right = right;
node->chunk_size = chunk_size;
node->depth = MAX(left->depth, right->depth) + 1;
node->char_count = left->char_count + right->char_count;
node->line_count = left->line_count + right->line_count;
return node;
} else {
Knot *node = (Knot *)malloc(sizeof(Knot) + chunk_size);
if (!node)
return nullptr;
node->left = nullptr;
node->right = nullptr;
node->chunk_size = chunk_size;
node->depth = 0;
node->char_count = len;
uint32_t newline_count = 0;
for (uint32_t i = 0; i < len; i++) {
char c = str[i];
node->data[i] = c;
if (c == '\n')
newline_count++;
}
node->line_count = newline_count;
return node;
}
}
// leaf if consumed and freed (so dont use or free it after)
// left and right are the new nodes
static void split_leaf(Knot *leaf, uint32_t k, Knot **left, Knot **right) {
Knot *left_node = (Knot *)malloc(sizeof(Knot) + leaf->chunk_size);
left_node->left = nullptr;
left_node->right = nullptr;
left_node->chunk_size = leaf->chunk_size;
left_node->depth = 0;
left_node->char_count = k;
uint32_t newline_count = 0;
for (uint32_t i = 0; i < k; i++) {
char c = leaf->data[i];
left_node->data[i] = c;
if (c == '\n')
newline_count++;
}
left_node->line_count = newline_count;
uint16_t right_line_count = leaf->line_count - newline_count;
*left = left_node;
Knot *right_node = (Knot *)malloc(sizeof(Knot) + leaf->chunk_size);
right_node->left = nullptr;
right_node->right = nullptr;
right_node->chunk_size = leaf->chunk_size;
right_node->depth = 0;
right_node->char_count = leaf->char_count - k;
right_node->line_count = right_line_count;
for (uint32_t i = k; i < leaf->char_count; i++) {
char c = leaf->data[i];
right_node->data[i - k] = c;
}
*right = right_node;
free(leaf);
}
// This makes node nonsensical, so dont use or free it after
void split(Knot *node, uint32_t offset, Knot **left, Knot **right) {
if (!node) {
*left = nullptr;
*right = nullptr;
return;
}
if (node->depth == 0) {
split_leaf(node, offset, left, right);
return;
}
uint32_t left_size = node->left ? node->left->char_count : 0;
if (offset < left_size) {
Knot *L = nullptr, *R = nullptr;
split(node->left, offset, &L, &R);
node->left = R;
update(node);
*right = node;
*left = L;
} else {
uint32_t new_offset = offset - left_size;
Knot *L = nullptr, *R = nullptr;
split(node->right, new_offset, &L, &R);
node->right = L;
update(node);
*left = node;
*right = R;
}
}
static inline int get_balance_factor(Knot *n) {
if (!n)
return 0;
return (int)DEPTH(n->left) - (int)DEPTH(n->right);
}
static inline Knot *rotate_right(Knot *y) {
Knot *x = y->left;
Knot *T2 = x->right;
x->right = y;
y->left = T2;
update(y);
update(x);
return x;
}
static inline Knot *rotate_left(Knot *x) {
Knot *y = x->right;
Knot *T2 = y->left;
y->left = x;
x->right = T2;
update(x);
update(y);
return y;
}
// Technically n can be used after calling
// but use return value instead
Knot *balance(Knot *n) {
update(n);
int bal = get_balance_factor(n);
if (bal > 1) {
if (get_balance_factor(n->left) < 0)
n->left = rotate_left(n->left);
return rotate_right(n);
}
if (bal < -1) {
if (get_balance_factor(n->right) > 0)
n->right = rotate_right(n->right);
return rotate_left(n);
}
return n;
}
// Dont free left or right after calling (only free return value)
// Assumes both ropes have equal chunk sizes
Knot *concat(Knot *left, Knot *right) {
if (!left)
return right;
if (!right)
return left;
if (!left || left->char_count == 0) {
if (left)
free_rope(left);
return right;
}
if (!right || right->char_count == 0) {
if (right)
free_rope(right);
return left;
}
if (left->depth == 0 && right->depth == 0) {
if (left->char_count + right->char_count <= left->chunk_size) {
Knot *node = (Knot *)malloc(sizeof(Knot) + left->chunk_size);
node->left = nullptr;
node->right = nullptr;
node->chunk_size = left->chunk_size;
node->depth = 0;
node->char_count = left->char_count + right->char_count;
node->line_count = left->line_count + right->line_count;
memcpy(node->data, left->data, left->char_count);
memcpy(node->data + left->char_count, right->data, right->char_count);
free(left);
free(right);
return node;
}
}
uint16_t d_left = left->depth;
uint16_t d_right = right->depth;
if (d_left > d_right + 1) {
left->right = concat(left->right, right);
return balance(left);
}
if (d_right > d_left + 1) {
right->left = concat(left, right->left);
return balance(right);
}
Knot *node = (Knot *)malloc(sizeof(Knot));
if (!node)
return nullptr;
node->left = left;
node->right = right;
node->chunk_size = left->chunk_size;
node->depth = MAX(d_left, d_right) + 1;
update(node);
return node;
}
// This makes node nonsensical, so dont use or free it after
// Instead, free the return value or use it in node's place
Knot *insert(Knot *node, uint32_t offset, char *str, uint32_t len) {
if (!node)
return nullptr;
if (node->depth == 0 && node->char_count + len <= node->chunk_size) {
if (offset < node->char_count)
memmove(node->data + offset + len, node->data + offset,
node->char_count - offset);
memcpy(node->data + offset, str, len);
node->char_count += len;
for (uint32_t i = 0; i < len; i++)
if (str[i] == '\n')
node->line_count++;
return node;
}
if (node->depth > 0) {
uint32_t left_count = node->left ? node->left->char_count : 0;
if (offset < left_count) {
Knot *new_left = insert(node->left, offset, str, len);
node->left = new_left;
update(node);
return balance(node);
} else {
Knot *new_right = insert(node->right, offset - left_count, str, len);
node->right = new_right;
update(node);
return balance(node);
}
}
Knot *left_part = nullptr;
Knot *right_part = nullptr;
split(node, offset, &left_part, &right_part);
Knot *middle_part = load(str, len, node->chunk_size);
return concat(concat(left_part, middle_part), right_part);
}
// This makes node nonsensical, so dont use or free it after
// Instead, free the return value or use it in node's place
Knot *erase(Knot *node, uint32_t offset, uint32_t len) {
if (!node || len == 0 || offset >= node->char_count)
return node;
if (offset + len > node->char_count)
len = node->char_count - offset;
if (node->depth == 0) {
uint32_t deleted_newlines = 0;
for (uint32_t i = offset; i < offset + len; i++)
if (node->data[i] == '\n')
deleted_newlines++;
node->line_count -= deleted_newlines;
if (offset + len < node->char_count)
memmove(node->data + offset, node->data + offset + len,
node->char_count - (offset + len));
node->char_count -= len;
return node;
}
uint32_t left_count = node->left ? node->left->char_count : 0;
if (offset + len <= left_count) {
node->left = erase(node->left, offset, len);
} else if (offset >= left_count) {
node->right = erase(node->right, offset - left_count, len);
} else {
Knot *left = nullptr, *middle = nullptr, *right = nullptr;
split(node, offset, &left, &right);
split(right, len, &middle, &right);
free_rope(middle);
return concat(left, right);
}
update(node);
return balance(node);
}
static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
if (!node || len == 0)
return;
if (node->depth == 0) {
memcpy(dest, node->data + offset, len);
return;
}
Knot *left = node->left;
uint32_t left_count = left ? left->char_count : 0;
if (offset < left_count) {
uint32_t chunk_len = left_count - offset;
if (chunk_len > len)
chunk_len = len;
_read_into(left, offset, chunk_len, dest);
dest += chunk_len;
len -= chunk_len;
offset = 0;
} else {
offset -= left_count;
}
if (len > 0 && node->right)
_read_into(node->right, offset, len, dest);
}
char *read(Knot *root, uint32_t offset, uint32_t len) {
if (!root)
return nullptr;
if (offset >= root->char_count) {
char *empty = (char *)malloc(1);
if (empty)
empty[0] = '\0';
return empty;
}
if (offset + len > root->char_count) {
len = root->char_count - offset;
}
char *buffer = (char *)malloc((len + 1) * sizeof(char));
if (!buffer)
return nullptr;
_read_into(root, offset, len, buffer);
buffer[len] = '\0';
return buffer;
}
// Hopefully free the tree only once at the end of its use using the pointer
// from the last insert or concat or erase call.
// (or use twice if last call was split - for both left and right).
void free_rope(Knot *root) {
if (!root)
return;
free_rope(root->left);
free_rope(root->right);
free(root);
}
static uint32_t find_nth_newline_offset(Knot *node, uint32_t n) {
if (!node || n > node->line_count)
return UINT32_MAX;
if (node->depth == 0) {
uint32_t count = 0;
for (uint32_t i = 0; i < node->char_count; i++) {
if (node->data[i] == '\n') {
if (count == n)
return i;
count++;
}
}
return UINT32_MAX;
}
uint32_t left_lines = node->left ? node->left->line_count : 0;
if (n < left_lines) {
return find_nth_newline_offset(node->left, n);
} else {
uint32_t right_offset =
find_nth_newline_offset(node->right, n - left_lines);
if (right_offset == UINT32_MAX)
return UINT32_MAX;
uint32_t left_chars = node->left ? node->left->char_count : 0;
return left_chars + right_offset;
}
}
uint32_t byte_to_line(Knot *node, uint32_t offset) {
if (!node)
return 0;
if (offset >= node->char_count)
return node->line_count;
if (node->depth == 0) {
uint32_t lines_before = 0;
uint32_t limit = (offset < node->char_count) ? offset : node->char_count;
for (uint32_t i = 0; i < limit; i++)
if (node->data[i] == '\n')
lines_before++;
return lines_before;
}
uint32_t left_chars = node->left ? node->left->char_count : 0;
if (offset < left_chars) {
return byte_to_line(node->left, offset);
} else {
uint32_t left_lines = node->left ? node->left->line_count : 0;
return left_lines + byte_to_line(node->right, offset - left_chars);
}
}
uint32_t line_to_byte(Knot *node, uint32_t line, uint32_t *out_len) {
if (!node) {
if (out_len)
*out_len = 0;
return 0;
}
uint32_t start_offset = 0;
uint32_t end_offset = 0;
if (line == 0) {
start_offset = 0;
} else {
uint32_t prev_newline = find_nth_newline_offset(node, line - 1);
if (prev_newline == UINT32_MAX)
start_offset = node->char_count;
else
start_offset = prev_newline + 1;
}
uint32_t current_newline = find_nth_newline_offset(node, line);
if (current_newline == UINT32_MAX)
end_offset = node->char_count;
else
end_offset = current_newline + 1;
if (out_len) {
if (end_offset > start_offset)
*out_len = end_offset - start_offset;
else
*out_len = 0;
}
return start_offset;
}
LineIterator *begin_l_iter(Knot *root, uint32_t start_line) {
if (!root)
return nullptr;
if (start_line > root->line_count)
return nullptr;
LineIterator *it = (LineIterator *)malloc(sizeof(LineIterator));
if (!it)
return nullptr;
it->top = 0;
it->line = start_line;
it->node = nullptr;
if (start_line == 0) {
it->offset = 0;
while (root->left) {
it->stack[it->top++] = root;
root = root->left;
if (!root->left && !root->right)
it->node = root;
}
it->stack[it->top++] = root;
return it;
}
Knot *curr = root;
uint32_t relative_line = start_line;
while (curr) {
it->stack[it->top++] = curr;
if (!curr->left && !curr->right) {
it->node = curr;
break;
}
uint32_t left_lines = (curr->left) ? curr->left->line_count : 0;
if (relative_line < left_lines) {
curr = curr->left;
} else {
relative_line -= left_lines;
curr = curr->right;
}
}
if (!it->node) {
free(it);
return nullptr;
}
it->offset = 0;
if (relative_line > 0) {
uint32_t found_newlines = 0;
uint32_t i = 0;
for (i = 0; i < it->node->char_count; i++) {
if (it->node->data[i] == '\n') {
found_newlines++;
if (found_newlines == relative_line) {
it->offset = i + 1;
break;
}
}
}
}
return it;
}
static inline void iter_advance_leaf(LineIterator *it) {
if (it->top == 0) {
it->node = nullptr;
return;
}
Knot *prev = it->stack[--it->top];
while (it->top > 0) {
Knot *parent = it->stack[it->top - 1];
if (parent->left == prev && parent->right) {
Knot *curr = parent->right;
while (curr) {
it->stack[it->top++] = curr;
if (!curr->left && !curr->right) {
it->node = curr;
it->offset = 0;
return;
}
curr = (curr->left) ? curr->left : curr->right;
}
}
prev = it->stack[--it->top];
}
it->node = nullptr;
}
char *next_line(LineIterator *it) {
if (!it || !it->node)
return nullptr;
size_t capacity = 128;
size_t len = 0;
char *buffer = (char *)malloc(capacity);
if (!buffer)
return nullptr;
while (it->node) {
if (it->offset >= it->node->char_count) {
iter_advance_leaf(it);
if (!it->node)
break;
}
char *start = it->node->data + it->offset;
char *end = it->node->data + it->node->char_count;
char *newline_ptr = (char *)memchr(start, '\n', end - start);
size_t chunk_len;
int found_newline = 0;
if (newline_ptr) {
chunk_len = (newline_ptr - start) + 1;
found_newline = 1;
} else {
chunk_len = end - start;
}
if (len + chunk_len + 1 > capacity) {
capacity = (capacity * 2) + chunk_len;
char *new_buf = (char *)realloc(buffer, capacity);
if (!new_buf) {
free(buffer);
return nullptr;
}
buffer = new_buf;
}
memcpy(buffer + len, start, chunk_len);
len += chunk_len;
it->offset += chunk_len;
if (found_newline) {
buffer[len] = '\0';
it->line++;
return buffer;
}
}
if (len > 0) {
buffer[len] = '\0';
it->line++;
return buffer;
}
free(buffer);
return nullptr;
}
LeafIterator *begin_k_iter(Knot *root) {
if (!root)
return nullptr;
LeafIterator *it = (LeafIterator *)malloc(sizeof(LeafIterator));
if (!it)
return nullptr;
it->top = 0;
Knot *curr = root;
while (curr) {
it->stack[it->top++] = curr;
if (!curr->left && !curr->right) {
it->node = curr;
return it;
}
curr = curr->left;
if (!curr) {
curr = it->stack[--it->top]->right;
Knot *temp = it->stack[it->top];
it->stack[it->top++] = temp;
curr = temp->left ? temp->left : temp->right;
Knot *parent = it->stack[it->top - 1];
curr = parent->left;
if (!curr) {
curr = parent->right;
}
}
}
free(it);
return nullptr;
}
// Caller must never free the returned string
char *next_leaf(LeafIterator *it) {
if (!it || !it->node)
return nullptr;
char *data_to_return = it->node->data;
data_to_return[it->node->char_count] = '\0';
Knot *prev_leaf = it->node;
Knot *parent = nullptr;
while (it->top > 0) {
parent = it->stack[--it->top];
if (parent->right && parent->right != prev_leaf) {
Knot *curr = parent->right;
while (curr) {
it->stack[it->top++] = curr;
if (!curr->left && !curr->right) {
it->node = curr;
return data_to_return;
}
curr = curr->left;
if (!curr)
curr = it->stack[it->top - 1]->right;
}
}
prev_leaf = parent;
}
it->node = nullptr;
return data_to_return;
}
ByteIterator *begin_b_iter(Knot *root) {
ByteIterator *b_it = (ByteIterator *)malloc(sizeof(ByteIterator));
LeafIterator *l_it = begin_k_iter(root);
b_it->it = l_it;
b_it->offset_g = 0;
b_it->offset_l = 0;
b_it->char_count = 0;
b_it->data = nullptr;
return b_it;
}
char next_byte(ByteIterator *it) {
if (it->data && it->offset_l < it->char_count) {
return it->data[it->offset_l++];
} else {
it->offset_g += it->offset_l;
it->offset_l = 1;
char *data = next_leaf(it->it);
it->char_count = strlen(data);
it->data = data;
if (it->data)
return *it->data;
else
return '\0';
}
}
std::vector<std::pair<size_t, size_t>> search_rope(Knot *root,
const char *pattern) {
std::vector<std::pair<size_t, size_t>> results;
int errorcode;
PCRE2_SIZE erroffset;
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0,
&errorcode, &erroffset, nullptr);
if (!re) {
fprintf(stderr, "PCRE2 compile error: %d\n", errorcode);
return results;
}
pcre2_match_data *mdata = pcre2_match_data_create(128, nullptr);
int workspace[PCRE_WORKSPACE_SIZE];
LeafIterator *it = begin_k_iter(root);
if (!it) {
pcre2_code_free(re);
pcre2_match_data_free(mdata);
return results;
}
size_t chunk_abs_offset = 0;
size_t saved_match_start = 0;
bool match_in_progress = false;
int flags = PCRE2_PARTIAL_SOFT;
while (1) {
const char *chunk_start = next_leaf(it);
if (!chunk_start)
break;
size_t chunk_len = strlen(chunk_start);
const char *current_ptr = chunk_start;
size_t remaining_len = chunk_len;
while (remaining_len > 0) {
int rc =
pcre2_dfa_match(re, (PCRE2_SPTR)current_ptr, remaining_len, 0, flags,
mdata, nullptr, workspace, PCRE_WORKSPACE_SIZE);
if (rc >= 0) {
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
size_t match_start_abs;
size_t match_end_abs;
if (match_in_progress) {
match_start_abs = saved_match_start;
match_end_abs =
chunk_abs_offset + (current_ptr - chunk_start) + ov[1];
} else {
match_start_abs =
chunk_abs_offset + (current_ptr - chunk_start) + ov[0];
match_end_abs =
chunk_abs_offset + (current_ptr - chunk_start) + ov[1];
}
size_t total_len = match_end_abs - match_start_abs;
results.push_back(std::make_pair(match_start_abs, total_len));
size_t consumed = ov[1];
if (consumed == 0)
consumed = 1;
current_ptr += consumed;
if (consumed > remaining_len)
remaining_len = 0;
else
remaining_len -= consumed;
match_in_progress = false;
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
continue;
} else if (rc == PCRE2_ERROR_PARTIAL) {
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
if (!match_in_progress) {
saved_match_start =
chunk_abs_offset + (current_ptr - chunk_start) + ov[0];
match_in_progress = true;
}
flags |= PCRE2_DFA_RESTART;
flags |= PCRE2_NOTBOL;
break;
} else {
if (match_in_progress) {
match_in_progress = false;
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
current_ptr++;
remaining_len--;
} else {
break;
}
// if (rc != PCRE2_ERROR_NOMATCH) {} // handle error
}
}
chunk_abs_offset += chunk_len;
if (!match_in_progress)
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
}
pcre2_match_data_free(mdata);
pcre2_code_free(re);
free(it);
return results;
}
uint32_t optimal_chunk_size(uint64_t length) {
if (length <= MIN_CHUNK_SIZE)
return MIN_CHUNK_SIZE;
double target_exponent = MIN(std::log2((double)MAX_CHUNK_SIZE),
7.0 + (std::log2((double)length) - 10.0) * 0.25);
uint32_t final_chunk_size =
MAX((uint32_t)MIN_CHUNK_SIZE, (uint32_t)std::pow(2.0, target_exponent));
final_chunk_size = MIN(final_chunk_size, (uint32_t)MAX_CHUNK_SIZE);
final_chunk_size = 1U << (32 - __builtin_clz(final_chunk_size - 1));
return final_chunk_size;
}
// Basic correctness test & usage example
int _main() {
char *buffer = (char *)malloc(44 * 4 + 5);
strcpy(buffer, "The quick brown fox jumps over the lazy dog.\n\
The quick brown fox jumps over the lazy dog.\n\
The quick brown fox jumps over the lazy dog.\n\
The quick brown fox jumps over the lazy dog.");
// This loads all (excluding \0 put in by strcpy)
Knot *root = load(buffer, 44 * 4 + 3, optimal_chunk_size(44 * 4 + 3));
Knot *left = nullptr, *right = nullptr;
// Splits root into left and right (root is no longer valid)
split(root, 5, &left, &right);
// simple read based on byte offset and length
char *s1 = read(left, 0, 100);
printf("%s\n:\n", s1);
char *s2 = read(right, 0, 100);
printf("%s\n;\n", s2);
free(s1);
free(s2);
// Recombines left and right into root (both can
// be valid or invalid in optimized cases)
// they are to not be used after concat
root = concat(left, right);
// root should be set to return value from insert always
root = insert(root, 5, buffer, 5);
free(buffer);
char *s3 = read(root, 0, 100);
printf("%s\n,\n", s3);
// Similar to insert but for erase
root = erase(root, 5, 5);
char *s4 = read(root, 0, 100);
printf("%s\n.\n", s4);
free(s3);
free(s4);
uint32_t byte_offset;
uint32_t len;
// Byte offset given reltive to how it would
// be in a file offset + len includes the \n
// at the end of the line (or nothing is EOF)
byte_offset = line_to_byte(root, 2, &len);
char *s5 = read(root, byte_offset, len);
printf("%s\n'\n", s5);
free(s5);
// returns line number of which line that
// byte position would be in.
// the ending \n position is included in this
uint32_t line = byte_to_line(root, byte_offset + len - 1);
printf("%u\n:\n", line);
// From second line onwards (0 indexed)
LineIterator *it = begin_l_iter(root, 0);
char *c = nullptr;
while ((c = next_line(it)) != nullptr) {
printf("%s :wow:\n", c);
free(c);
}
free(it);
printf("\n/\n");
// Starts at first byte (to be used for regex search)
ByteIterator *it2 = begin_b_iter(root);
uint32_t saved[40];
for (int i = 0; i < 40; i++)
saved[i] = 0;
std::string pattern = "f.x";
Inst *program = compile_regex(pattern);
bool result;
while ((result = next_match(program, it2, saved))) {
printf("\nRES: %d\n", result);
for (int i = 0; i < 40; i++)
printf("%d, ", saved[i]);
}
// char c2 = ' ';
// while ((c2 = next_byte(it2)) != '\0')
// printf("%c :wow!:\n", c2);
// free(it2);
// search // uses leaf iterator internally // PCRE2 based
std::vector<std::pair<size_t, size_t>> matches = search_rope(root, "f.x");
for (size_t i = 0; i < matches.size(); i++)
printf("\n%lu %lu", matches[i].first, matches[i].second);
// A rope needs to be freed only once if last action on the rope is
// insert or concat or erase.
// for splits we need to free both left and right separately
free_rope(root);
return 0;
}