diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2021-01-15 19:27:25 -0800 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2021-01-15 19:27:25 -0800 |
| commit | 77b33d6a3cdc2655fa0319a2c5a077eb709cb6aa (patch) | |
| tree | f4d29634e49c71f4688bcb08b6ec8da40d00be31 /files.c | |
| parent | 10dbcdd4fd7bf2f14d49bdf19139f8dd5d53aebd (diff) | |
Renaming files: printing->print, file_loader->files
Diffstat (limited to 'files.c')
| -rw-r--r-- | files.c | 230 |
1 files changed, 230 insertions, 0 deletions
@@ -0,0 +1,230 @@ +// +// files.c - Implementation of some file loading functionality. +// + +#include <ctype.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "files.h" +#include "utils.h" + +__attribute__((nonnull)) +static void populate_lines(file_t *f); + +// +// In the file object, populate the `lines` array with pointers to the +// beginning of each line. +// +static void populate_lines(file_t *f) +{ + // Calculate line numbers: + size_t linecap = 10; + f->lines = xcalloc(sizeof(const char*), linecap); + f->nlines = 0; + char *p = f->contents; + for (size_t n = 0; p && p < f->end; ++n) { + ++f->nlines; + if (n >= linecap) + f->lines = xrealloc(f->lines, sizeof(const char*)*(linecap *= 2)); + f->lines[n] = p; + p = strchr(p, '\n'); + if (p) ++p; + } +} + +// +// Read an entire file into memory. +// +file_t *load_file(file_t **files, const char *fmt, ...) +{ + char filename[PATH_MAX+1] = {0}; + va_list args; + va_start(args, fmt); + check(vsnprintf(filename, PATH_MAX, fmt, args) <= PATH_MAX, + "File name is too large"); + va_end(args); + + int fd = filename[0] == '\0' ? STDIN_FILENO : open(filename, O_RDONLY); + if (fd < 0) return NULL; + size_t length; + file_t *f = new(file_t); + f->filename = strdup(filename); + + struct stat sb; + if (fstat(fd, &sb) == -1) + goto skip_mmap; + + f->contents = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (f->contents == MAP_FAILED) + goto skip_mmap; + + f->mmapped = 1; + length = (size_t)sb.st_size; + goto finished_loading; + + skip_mmap: + f->mmapped = 0; + size_t capacity = 1000; + length = 0; + f->contents = xcalloc(sizeof(char), capacity); + ssize_t just_read; + while ((just_read=read(fd, &f->contents[length], capacity - length)) > 0) { + length += (size_t)just_read; + if (length >= capacity) + f->contents = xrealloc(f->contents, sizeof(char)*(capacity *= 2) + 1); + } + if (fd != STDIN_FILENO) close(fd); + + finished_loading: + f->end = &f->contents[length]; + populate_lines(f); + if (files != NULL) { + f->next = *files; + *files = f; + } + return f; +} + +// +// Create a virtual file from a string. +// +file_t *spoof_file(file_t **files, const char *filename, const char *text) +{ + if (filename == NULL) filename = ""; + file_t *f = new(file_t); + f->filename = strdup(filename); + f->contents = strdup(text); + f->end = &f->contents[strlen(text)]; + populate_lines(f); + if (files != NULL) { + f->next = *files; + *files = f; + } + return f; +} + +// +// Ensure that the file's contents are held in memory, rather than being memory +// mapped IO. +// +void intern_file(file_t *f) +{ + if (!f->mmapped) return; + size_t size = (size_t)(f->end - f->contents); + char *buf = xcalloc(sizeof(char), size + 1); + memcpy(buf, f->contents, size); + munmap(f->contents, size); + f->contents = buf; + f->end = buf + size; + f->mmapped = 0; + xfree(&f->lines); + populate_lines(f); +} + +// +// Free a file and all memory contained inside its members, then set the input +// pointer to NULL. +// +void destroy_file(file_t **f) +{ + if ((*f)->filename) { + xfree(&((*f)->filename)); + } + + if ((*f)->lines) { + xfree(&((*f)->lines)); + } + + if ((*f)->contents) { + if ((*f)->mmapped) { + munmap((*f)->contents, (size_t)((*f)->end - (*f)->contents)); + (*f)->contents = NULL; + } else { + xfree(&((*f)->contents)); + } + } + + for (allocated_pat_t *next; (*f)->pats; (*f)->pats = next) { + next = (*f)->pats->next; + destroy_pat(&(*f)->pats->pat); + xfree(&(*f)->pats); + } + + xfree(f); +} + +// +// Given a pointer, determine which line number it points to. +// +size_t get_line_number(file_t *f, const char *p) +{ + // TODO: binary search + for (size_t n = 1; n < f->nlines; n++) { + if (f->lines[n] > p) + return n; + } + return f->nlines; +} + +// +// Given a pointer, determine which character offset within the line it points to. +// +size_t get_char_number(file_t *f, const char *p) +{ + size_t linenum = get_line_number(f, p); + return 1 + (size_t)(p - f->lines[linenum-1]); +} + +// +// Return a pointer to the line with the specified line number. +// +const char *get_line(file_t *f, size_t line_number) +{ + if (line_number == 0 || line_number > f->nlines) return NULL; + return f->lines[line_number - 1]; +} + +// +// Print the filename/line number, followed by the given message, followed by +// the line itself. +// +void fprint_line(FILE *dest, file_t *f, const char *start, const char *end, const char *fmt, ...) +{ + if (start < f->contents) start = f->contents; + if (start > f->end) start = f->end; + if (end < f->contents) end = f->contents; + if (end > f->end) end = f->end; + size_t linenum = get_line_number(f, start); + const char *line = get_line(f, linenum); + size_t charnum = get_char_number(f, start); + fprintf(dest, "\033[1m%s:%ld:\033[0m ", f->filename, linenum); + + va_list args; + va_start(args, fmt); + vfprintf(dest, fmt, args); + va_end(args); + fputc('\n', dest); + + const char *eol = linenum == f->nlines ? strchr(line, '\0') : strchr(line, '\n'); + if (end == NULL || end > eol) end = eol; + fprintf(dest, "\033[2m% 5ld |\033[0m %.*s\033[41;30m%.*s\033[0m%.*s\n", + linenum, + (int)charnum - 1, line, + (int)(end - &line[charnum-1]), &line[charnum-1], + (int)(eol - end), end); + fprintf(dest, " \033[34;1m"); + const char *p = line - 1; + for (; p < start; ++p) fputc(' ', dest); + if (start == end) ++end; + for (; p < end; ++p) fputc('^', dest); + fprintf(dest, "\033[0m\n"); +} + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1 |
