123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 | // Implementation of some file loading functionality.
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <gc.h>
#include <libgen.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include "files.h"
#include "print.h"
#include "util.h"
static const int tabstop = 4;
public
char *resolve_path(const char *path, const char *relative_to, const char *system_path) {
if (!relative_to || streq(relative_to, "/dev/stdin")) relative_to = ".";
if (!path || strlen(path) == 0) return NULL;
// Resolve the path to an absolute path, assuming it's relative to the file
// it was found in:
char buf[PATH_MAX] = {0};
if (streq(path, "~") || starts_with(path, "~/")) {
char *resolved = realpath(String(getenv("HOME"), path + 1), buf);
if (resolved) return GC_strdup(resolved);
} else if (streq(path, ".") || starts_with(path, "./") || starts_with(path, "../")) {
char *relative_dir = dirname(GC_strdup(relative_to));
char *resolved = realpath(String(relative_dir, "/", relative_dir, path), buf);
if (resolved) return GC_strdup(resolved);
} else if (path[0] == '/') {
// Absolute path:
char *resolved = realpath(path, buf);
if (resolved) return GC_strdup(resolved);
} else {
// Relative path:
char *relative_dir = dirname(GC_strdup(relative_to));
if (!system_path) system_path = ".";
char *copy = GC_strdup(system_path);
for (char *dir, *pos = copy; (dir = strtok(pos, ":")); pos = NULL) {
if (dir[0] == '/') {
char *resolved = realpath(String(dir, "/", path), buf);
if (resolved) return GC_strdup(resolved);
} else if (dir[0] == '~' && (dir[1] == '\0' || dir[1] == '/')) {
char *resolved = realpath(String(getenv("HOME"), dir + 1, "/", path), buf);
if (resolved) return GC_strdup(resolved);
} else if (streq(dir, ".") || strncmp(dir, "./", 2) == 0) {
char *resolved = realpath(String(relative_dir, "/", path), buf);
if (resolved) return GC_strdup(resolved);
} else if (streq(dir, ".") || streq(dir, "..") || strncmp(dir, "./", 2) == 0
|| strncmp(dir, "../", 3) == 0) {
char *resolved = realpath(String(relative_dir, "/", dir, "/", path), buf);
if (resolved) return GC_strdup(resolved);
} else {
char *resolved = realpath(String(dir, "/", path), buf);
if (resolved) return GC_strdup(resolved);
}
}
}
return NULL;
}
public
char *file_base_name(const char *path) {
const char *slash = strrchr(path, '/');
if (slash) path = slash + 1;
assert(!isdigit(*path));
const char *end = path + strcspn(path, ".");
size_t len = (size_t)(end - path);
char *buf = GC_MALLOC_ATOMIC(len + 1);
strncpy(buf, path, len);
buf[len] = '\0';
return buf;
}
static file_t *_load_file(const char *filename, FILE *file) {
if (!file) return NULL;
file_t *ret = new (file_t, .filename = filename);
size_t file_size = 0, line_cap = 0;
char *file_buf = NULL, *line_buf = NULL;
FILE *mem = open_memstream(&file_buf, &file_size);
int64_t line_len = 0;
while ((line_len = getline(&line_buf, &line_cap, file)) >= 0) {
if (ret->line_capacity <= ret->num_lines)
ret->line_offsets = GC_REALLOC(ret->line_offsets, sizeof(int64_t[ret->line_capacity += 32]));
ret->line_offsets[ret->num_lines++] = (int64_t)file_size;
fwrite(line_buf, sizeof(char), (size_t)line_len, mem);
fflush(mem);
}
fclose(file);
char *copy = GC_MALLOC_ATOMIC(file_size + 1);
memcpy(copy, file_buf, file_size);
copy[file_size] = '\0';
ret->text = copy;
ret->len = (int64_t)file_size;
fclose(mem);
free(file_buf);
ret->relative_filename = filename;
if (filename && filename[0] != '<' && !streq(filename, "/dev/stdin")) {
filename = resolve_path(filename, ".", ".");
// Convert to relative path (if applicable)
char buf[PATH_MAX];
char *cwd = getcwd(buf, sizeof(buf));
if (cwd != NULL) {
size_t cwd_len = strlen(cwd);
if (strncmp(cwd, filename, cwd_len) == 0 && filename[cwd_len] == '/')
ret->relative_filename = &filename[cwd_len + 1];
}
}
if (line_buf != NULL) free(line_buf);
return ret;
}
//
// Read an entire file into memory.
//
public
file_t *load_file(const char *filename) {
FILE *file = filename[0] ? fopen(filename, "r") : stdin;
return _load_file(filename, file);
}
//
// Create a virtual file from a string.
//
public
file_t *spoof_file(const char *filename, const char *text) {
FILE *file = fmemopen((char *)text, strlen(text) + 1, "r");
return _load_file(filename, file);
}
//
// Given a pointer, determine which line number it points to (1-indexed)
//
public
int64_t get_line_number(file_t *f, const char *p) {
// Binary search:
int64_t lo = 0, hi = (int64_t)f->num_lines - 1;
if (p < f->text) return 0;
int64_t offset = (int64_t)(p - f->text);
while (lo <= hi) {
int64_t mid = (lo + hi) / 2;
int64_t line_offset = f->line_offsets[mid];
if (line_offset == offset) return mid + 1;
else if (line_offset < offset) lo = mid + 1;
else if (line_offset > offset) hi = mid - 1;
}
return lo; // Return the line number whose line starts closest before p
}
//
// Given a pointer, determine which line column it points to.
//
public
int64_t get_line_column(file_t *f, const char *p) {
int64_t line_no = get_line_number(f, p);
int64_t line_offset = f->line_offsets[line_no - 1];
return 1 + (int64_t)(p - (f->text + line_offset));
}
//
// Return a pointer to the line with the specified line number (1-indexed)
//
public
const char *get_line(file_t *f, int64_t line_number) {
if (line_number == 0 || line_number > (int64_t)f->num_lines) return NULL;
int64_t line_offset = f->line_offsets[line_number - 1];
return f->text + line_offset;
}
//
// Return a value like /foo:line.col
//
public
const char *get_file_pos(file_t *f, const char *p) {
return String(f->filename, ":", get_line_number(f, p), ".", get_line_column(f, p));
}
static int fputc_column(FILE *out, char c, char print_char, int *column) {
int printed = 0;
if (print_char == '\t') print_char = ' ';
if (c == '\t') {
for (int to_fill = tabstop - (*column % tabstop); to_fill > 0; --to_fill) {
printed += fputc(print_char, out);
++*column;
}
} else {
printed += fputc(print_char, out);
++*column;
}
return printed;
}
//
// Print a span from a file
//
public
int highlight_error(file_t *file, const char *start, const char *end, const char *hl_color, int64_t context_lines,
bool use_color) {
if (!file) return 0;
// Handle spans that come from multiple files:
if (start < file->text || start > file->text + file->len) start = end;
if (end < file->text || end > file->text + file->len) end = start;
// Just in case neither end of the span came from this file:
if (end < file->text || end > file->text + file->len) start = end = file->text;
const char *lineno_prefix, *lineno_suffix, *normal_color, *empty_marker;
bool print_carets = false;
int printed = 0;
if (use_color) {
lineno_prefix = "\x1b[0;2m";
lineno_suffix = "\x1b(0\x78\x1b(B\x1b[m ";
normal_color = "\x1b[m";
empty_marker = "\x1b(0\x61\x1b(B";
printed += fprint(stderr, "\x1b[33;4;1m", file->relative_filename, "\x1b[m");
} else {
lineno_prefix = "";
lineno_suffix = "| ";
hl_color = "";
normal_color = "";
empty_marker = " ";
print_carets = true;
printed += fprint(stderr, file->relative_filename);
}
if (context_lines == 0) return fprint(stderr, hl_color, string_slice(start, (size_t)(end - start)), normal_color);
int64_t start_line = get_line_number(file, start), end_line = get_line_number(file, end);
int64_t first_line = start_line - (context_lines - 1), last_line = end_line + (context_lines - 1);
if (first_line < 1) first_line = 1;
if (last_line > file->num_lines) last_line = file->num_lines;
int digits = 1;
for (int64_t i = last_line; i > 0; i /= 10)
++digits;
for (int64_t line_no = first_line; line_no <= last_line; ++line_no) {
if (line_no > first_line + 5 && line_no < last_line - 5) {
if (use_color)
printed += fprint(stderr, "\x1b[0;2;3;4m ... ", (last_line - first_line) - 11,
" lines omitted ... \x1b[m");
else printed += fprint(stderr, " ... ", (last_line - first_line) - 11, " lines omitted ...");
line_no = last_line - 6;
continue;
}
int needed_spaces = digits;
for (int64_t n = line_no; n > 0; n /= 10)
needed_spaces -= 1;
printed += fprint_inline(stderr, lineno_prefix, repeated_char(' ', needed_spaces), line_no, lineno_suffix);
const char *line = get_line(file, line_no);
if (!line) break;
int column = 0;
const char *p = line;
// Before match
for (; *p && *p != '\r' && *p != '\n' && p < start; ++p)
printed += fputc_column(stderr, *p, *p, &column);
// Zero-width matches
if (p == start && start == end) {
printed += fprint_inline(stderr, hl_color, empty_marker, normal_color);
column += 1;
}
// Inside match
if (start <= p && p < end) {
printed += fputs(hl_color, stderr);
for (; *p && *p != '\r' && *p != '\n' && p < end; ++p)
printed += fputc_column(stderr, *p, *p, &column);
printed += fputs(normal_color, stderr);
}
// After match
for (; *p && *p != '\r' && *p != '\n'; ++p)
printed += fputc_column(stderr, *p, *p, &column);
printed += fprint_inline(stderr, "\n");
const char *eol = line + strcspn(line, "\r\n");
if (print_carets && start >= line && start < eol && line <= start) {
for (int num = 0; num < digits; num++)
printed += fputc(' ', stderr);
printed += fputs(": ", stderr);
int col = 0;
for (const char *sp = line; *sp && *sp != '\n'; ++sp) {
char print_char;
if (sp < start) print_char = ' ';
else if (sp == start && sp == end) print_char = '^';
else if (sp >= start && sp < end) print_char = '-';
else print_char = ' ';
printed += fputc_column(stderr, *sp, print_char, &col);
}
printed += fputs("\n", stderr);
}
}
fflush(stderr);
return printed;
}
|