ruby/yarp/util/yp_newline_list.c
Aaron Patterson abce8583e2 [ruby/yarp] Fix heredocs inside %W and %w lists
The problem was that we were treating heredoc bodies as part of the %W
list because we didn't push the scanning cursor past the heredoc after
lexing out the here doc.  To fix this, we changed the whitespace
scanning function to quit scanning when it reaches a newline but only in
the case that a heredoc is present.

Additionally, we need to prevent double counting newlines in the case of
a heredoc.  For example:

```ruby
%W(<<foo 123)
foo
```

The newline after the `)` is counted as part of scanning the heredoc, so
we added logic to prevent double counting the newline when scanning the
rest of the %W list.

eb090d8126

Co-authored-by: Jemma Issroff <jemmaissroff@gmail.com>
2023-07-20 14:58:11 +00:00

119 lines
3.7 KiB
C

#include "yarp/util/yp_newline_list.h"
// Initialize a new newline list with the given capacity. Returns true if the
// allocation of the offsets succeeds, otherwise returns false.
bool
yp_newline_list_init(yp_newline_list_t *list, const char *start, size_t capacity) {
list->offsets = (size_t *) calloc(capacity, sizeof(size_t));
if (list->offsets == NULL) return false;
list->start = start;
// This is 1 instead of 0 because we want to include the first line of the
// file as having offset 0, which is set because of calloc.
list->size = 1;
list->capacity = capacity;
list->last_index = 0;
list->last_offset = 0;
return true;
}
// Append a new offset to the newline list. Returns true if the reallocation of
// the offsets succeeds (if one was necessary), otherwise returns false.
bool
yp_newline_list_append(yp_newline_list_t *list, const char *cursor) {
if (list->size == list->capacity) {
list->capacity = (list->capacity * 3) / 2;
list->offsets = (size_t *) realloc(list->offsets, list->capacity * sizeof(size_t));
if (list->offsets == NULL) return false;
}
assert(cursor >= list->start);
size_t newline_offset = (size_t) (cursor - list->start + 1);
assert(list->size == 0 || newline_offset > list->offsets[list->size - 1]);
list->offsets[list->size++] = newline_offset;
return true;
}
// Returns the line and column of the given offset, assuming we don't have any
// information about the previous index that we found.
static yp_line_column_t
yp_newline_list_line_column_search(yp_newline_list_t *list, size_t offset) {
size_t left = 0;
size_t right = list->size - 1;
while (left <= right) {
size_t mid = left + (right - left) / 2;
if (list->offsets[mid] == offset) {
return ((yp_line_column_t) { mid, 0 });
}
if (list->offsets[mid] < offset) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return ((yp_line_column_t) { left - 1, offset - list->offsets[left - 1] });
}
// Returns the line and column of the given offset, assuming we know the last
// index that we found.
static yp_line_column_t
yp_newline_list_line_column_scan(yp_newline_list_t *list, size_t offset) {
if (offset > list->last_offset) {
size_t index = list->last_index;
while (index < list->size && list->offsets[index] < offset) {
index++;
}
if (index == list->size) {
return ((yp_line_column_t) { index - 1, offset - list->offsets[index - 1] });
}
return ((yp_line_column_t) { index, 0 });
} else {
size_t index = list->last_index;
while (index > 0 && list->offsets[index] > offset) {
index--;
}
if (index == 0) {
return ((yp_line_column_t) { 0, offset });
}
return ((yp_line_column_t) { index, offset - list->offsets[index - 1] });
}
}
// Returns the line and column of the given offset. If the offset is not in the
// list, the line and column of the closest offset less than the given offset
// are returned.
yp_line_column_t
yp_newline_list_line_column(yp_newline_list_t *list, const char *cursor) {
assert(cursor >= list->start);
size_t offset = (size_t) (cursor - list->start);
yp_line_column_t result;
if (list->last_offset == 0) {
result = yp_newline_list_line_column_search(list, offset);
} else {
result = yp_newline_list_line_column_scan(list, offset);
}
list->last_index = result.line;
list->last_offset = offset;
return result;
}
// Free the internal memory allocated for the newline list.
void
yp_newline_list_free(yp_newline_list_t *list) {
free(list->offsets);
}