/* parc * Copyright (C) 2025 Ekaitz Zarraga * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include "piece-table-internals.h" /* text_buffer */ void text_buffer_init (text_buffer *tb) { tb->used = 0; tb->size = 0; tb->contents = NULL; } void text_buffer_resize (text_buffer *tb, size_t size) { tb->size = size; tb->contents = realloc (tb->contents, sizeof (*tb->contents) * size); } void text_buffer_append (text_buffer *tb, char c) { size_t base_size = 1024; if (tb->size == 0) { tb->contents = malloc (sizeof (*tb->contents) * base_size); tb->size = base_size; } else if (tb->size == tb->used) text_buffer_resize (tb, tb->size * 2); tb->contents[tb->used++] = c; } char text_buffer_index (text_buffer *tb, size_t pos) { assert (pos < tb->used); return tb->contents[pos]; } void text_buffer_empty (text_buffer *tb) { free (tb->contents); text_buffer_init (tb); } void text_buffer_fill (text_buffer *tb, char *fill, size_t size) { size_t i; tb->used = size; tb->size = size; free (tb->contents); tb->contents = malloc (sizeof (*tb->contents) * size); for (i=0; icontents[i] = fill[i]; } } void text_buffer_free (text_buffer *tb) { free (tb->contents); text_buffer_init (tb); } /* piece_buffer */ piece_buffer * piece_buffer_create (size_t size) { piece_buffer *pb; size_t s = sizeof (piece_buffer) + sizeof (piece) * size; pb = malloc (s); memset (pb, 0, s); pb->start = (piece *)pb+1; return pb; } void piece_buffer_destroy (piece_buffer *pb) { piece_buffer *next; do { next = pb->next; free (pb); pb = next; } while (pb != NULL); } void piece_buffer_empty (piece_buffer *pb) { piece_buffer_destroy (pb->next); pb->start -= pb->used; } piece * piece_buffer_bump (piece_buffer *pb) { while (pb->next != NULL) { pb = pb->next; } if (pb->used == pb->size) { pb->next = piece_buffer_create (1024); } return &pb->start[pb->used++]; } /* piece_table */ void piece_table_piece_mark_empty (piece_table *pt, piece *p) { p->next = pt->empty; pt->empty = p; } piece * piece_table_piece_new (piece_table *pt) { piece *p; if (pt->empty != NULL) { p = pt->empty; pt->empty = pt->empty->next; return p; } /* TODO: Resize if needed */ return piece_buffer_bump (pt->pieces); } piece * piece_table_piece_split (piece_table *pt, piece *p, size_t pos) { /* Returns the first piece */ piece *second; assert (pos < p->length); assert (p->buffer != NULL); /* Not a sentinel piece */ assert (p->length != 0); /* Not an empty piece (they should not exist) */ if ( pos == 0 ) { return p->prev; } second = piece_buffer_bump (pt->pieces); second->length = p->length - pos; p->length = pos; second->next = p->next; second->next->prev = second; p->next = second; second->prev = p; second->start = p->start + pos; second->buffer = p->buffer; return p; } void piece_table_piece_find (piece_table *pt, size_t pos) { piece *cur; assert (pos < pt->length); if ( pos >= pt->cached_offset ) { /* Search forward */ for ( cur = pt->cached; ; cur = cur->next ) { if ( pt->cached_offset + cur->length > pos ) { pt->cached = cur; assert (pt->cached != pt->sentinel); return; } pt->cached_offset += cur->length; } } else { /* Search backwards */ for ( cur = pt->cached->prev; ; cur = cur->prev ) { pt->cached_offset -= cur->length; if ( pt->cached_offset <= pos ) { pt->cached = cur; assert (pt->cached != pt->sentinel); return; } } } } void piece_table_insert (piece_table *pt, size_t pos, char *in, size_t len) { piece *start, *end, *new; size_t i; assert ( pos <= pt->length ); assert ( len > 0 ); for (i = 0; iadd, in[i]); } if ( pos == 0 ) { start = pt->sentinel; } else if ( pos == pt->length ) { start = pt->sentinel->prev; pt->cached_offset = pt->length - start->length; pt->cached = start; } else { piece_table_piece_find (pt, pos); start = piece_table_piece_split (pt, pt->cached, pos - pt->cached_offset); } end = start->next; /* Update cache; * If we insert before our cached piece, the offset is not up-to-date * anymore. */ if ( pt->cached_offset >= pos ) { pt->cached = pt->cached->prev; pt->cached_offset -= pt->cached->length; } /* Optimization! * Add in the end of the buffer, and the end of the piece: enlarge */ if ( pt->add.used != 0 && start->buffer == &pt->add && pt->cached_offset + start->length == pos && pt->add.used == start->start + start->length ) { pt->length += len; start->length += len; return; } /* Make a new piece and insert it */ new = piece_buffer_bump (pt->pieces); new->buffer = &pt->add; new->start = pt->add.used - len; new->length = len; start->next = new; new->prev = start; new->next = end; end->prev = new; pt->length += len; } void piece_table_delete (piece_table *pt, size_t pos, size_t len) { piece *cur, *next, *start, *end; size_t off_start; assert ( pos + len <= pt->length ); assert ( len > 0 ); if ( pos == 0 ) { start = pt->sentinel; off_start = 0; } else { piece_table_piece_find (pt, pos); off_start = pt->cached_offset; start = piece_table_piece_split (pt, pt->cached, pos - pt->cached_offset); } if ( pos + len == pt->length ) { end = pt->sentinel; } else { piece_table_piece_find (pt, pos + len); end = piece_table_piece_split (pt, pt->cached, pos + len - pt->cached_offset)->next; } /* Update cache to before the start */ if ( pt->cached_offset >= pos ) { pt->cached = start; pt->cached_offset = off_start; } /* Free unneeded pieces */ for ( cur = start->next; cur != end ; cur = next ) { next = cur->next; /* Free the piece! */ piece_table_piece_mark_empty (pt, cur); } start->next = end; end->prev = start; pt->length -= len; } piece_table * piece_table_create_from (char *orig, size_t size) { piece *original; piece_table * pt = malloc (sizeof (*pt)); pt->length = size; /* Original buffer */ text_buffer_init (&pt->orig); text_buffer_fill (&pt->orig, orig, size); /* Add buffer */ text_buffer_init (&pt->add); pt->pieces = piece_buffer_create (1024); pt->sentinel = piece_buffer_bump (pt->pieces); original = piece_buffer_bump (pt->pieces); original->buffer = &pt->orig; original->start = 0; original->length = pt->length; original->next = pt->sentinel; original->prev = pt->sentinel; pt->sentinel->next = original; pt->sentinel->prev = original; pt->empty = NULL; pt->cached = original; pt->cached_offset = 0; return pt; } piece_table * piece_table_create (char *orig, size_t size) { return piece_table_create_from ("", 0); } void piece_table_destroy (piece_table *pt) { text_buffer_free (&pt->orig); text_buffer_free (&pt->add); piece_buffer_destroy (pt->pieces); free (pt); } char piece_table_index (piece_table *pt, size_t pos) { assert (pos < pt->length); piece_table_piece_find (pt, pos); return text_buffer_index (pt->cached->buffer, pt->cached->start + pos - pt->cached_offset); } void piece_table_to_string (piece_table *pt, char *buf, size_t size) { size_t i; if (pt->length < size) size = pt->length; for (i = 0; i < size; i++) { buf[i] = piece_table_index (pt, i); } buf[i] = '\0'; } size_t piece_table_length (piece_table *pt) { return pt->length; }