aboutsummaryrefslogtreecommitdiffhomepage
path: root/io.c
diff options
context:
space:
mode:
authorlemon <lsof@mailbox.org>2023-05-10 20:38:32 +0200
committerlemon <lsof@mailbox.org>2023-05-10 20:38:32 +0200
commit9100ed2b5dd01df8e6b766c7bc2a12c0dd44f1ff (patch)
tree0598b126bdddb7db462a2f0915e272d4345c0c39 /io.c
initial commit
Diffstat (limited to 'io.c')
-rw-r--r--io.c836
1 files changed, 836 insertions, 0 deletions
diff --git a/io.c b/io.c
new file mode 100644
index 0000000..83b15b8
--- /dev/null
+++ b/io.c
@@ -0,0 +1,836 @@
+#include "parse.h"
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+#include <errno.h>
+
+static char stdoutbuf[1024], stderrbuf[1024];
+struct wbuf bstdout = FDBUF(stdoutbuf, sizeof stdoutbuf, 1),
+ bstderr = FDBUF(stderrbuf, sizeof stderrbuf, 2);
+
+void
+iowrite(struct wbuf *buf, const void *Src, int n)
+{
+ const uchar *src = Src;
+
+ while (n > 0) {
+ int avail = buf->cap - buf->len;
+ int amt = avail < n ? avail : n;
+
+ memcpy(buf->buf + buf->len, src, amt);
+ n -= amt;
+ if ((buf->len += amt) == buf->cap) {
+ if (buf->fd < 0) {
+ buf->err = 1;
+ return;
+ }
+ ioflush(buf);
+ }
+ }
+}
+
+void
+ioflush(struct wbuf *buf)
+{
+ int i, ret;
+
+ buf->err = 0;
+ if (buf->fd < 0) {
+ buf->len = 0;
+ return;
+ }
+ for (i = 0; buf->len > 0;) {
+ ret = write(buf->fd, buf->buf + i, buf->len);
+ if (ret > 0) {
+ buf->len -= ret;
+ i += ret;
+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ continue;
+ } else {
+ buf->err = 1;
+ break;
+ }
+ }
+}
+
+void
+ioputc(struct wbuf *buf, uchar c)
+{
+ if (buf->len == buf->cap) {
+ if (buf->fd < 0) {
+ buf->err = 1;
+ return;
+ }
+ ioflush(buf);
+ }
+ buf->buf[buf->len++] = c;
+}
+
+static int
+putquoted(struct wbuf *buf, uchar c, uchar qchar, int next)
+{
+ if (c == qchar || c == '\\' || !aisprint(c)) {
+ int n = (ioputc(buf, '\\'), 1);
+ uchar cseq;
+
+ switch (c) {
+ case '\\':
+ case '\'':
+ case '"':
+ cseq = c;
+ Charseq:
+ n += (ioputc(buf, cseq), 1);
+ break;
+ case '\a': cseq = 'a'; goto Charseq;
+ case '\b': cseq = 'b'; goto Charseq;
+ case '\f': cseq = 'f'; goto Charseq;
+ case '\r': cseq = 'r'; goto Charseq;
+ case '\t': cseq = 't'; goto Charseq;
+ case '\v': cseq = 'v'; goto Charseq;
+ case '\n': cseq = 'n'; goto Charseq;
+ default:
+ if (in_range(next, '0', '7'))
+ n += bfmt(buf, "%.3o", c);
+ else
+ n += bfmt(buf, "%o", c);
+ }
+ return n;
+ }
+ if (c == '?' && next == '?') {
+ return ioputc(buf, c), ioputc(buf, '\\'), 2;
+ }
+ return ioputc(buf, c), 1;
+}
+
+static int
+putuint(struct wbuf *buf, uvlong x, int base, bool lower)
+{
+ uchar tmp[64];
+ uchar *end = tmp + sizeof(tmp);
+ uchar *s = end;
+ switch (base) {
+ case 2:
+ do *--s = '0' + x%2; while (x >>= 1);
+ break;
+ case 8:
+ do *--s = '0' + x%8; while (x >>= 3);
+ break;
+ case 10:
+ do *--s = '0' + x%10; while (x /= 10);
+ break;
+ case 16:
+ do *--s = "0123456789ABCDEF"[x&15] | (-lower & 0x20); while (x >>= 4);
+ break;
+ default:
+ assert(0&&"base");
+ }
+ iowrite(buf, s, end - s);
+ return end - s;
+}
+
+static void
+fmterr(const char *fmt, ...)
+{
+ va_list ap;
+
+ efmt("fmt Error: ");
+ va_start(ap, fmt);
+ vbfmt(&bstderr, fmt, ap);
+ va_end(ap);
+ ioputc(&bstderr, '\n');
+ ioflush(&bstderr);
+ ioflush(&bstdout);
+ abort();
+}
+
+#define bwriteS(buf, S) (iowrite(buf, S, sizeof S - 1), sizeof S - 1)
+#define bputc(B, C) (ioputc(B, C), 1)
+
+static int
+priquals(struct wbuf *buf, int q)
+{
+ const char s[] = " const volatile", *p = s;
+ int m = sizeof s - 1;
+ if (!q) return 0;
+ else if (q == QCONST) m -= 9;
+ else if (q == QVOLATILE) p += 6, m -= 6;
+ else assert(q == (QCONST | QVOLATILE));
+ iowrite(buf, p, m);
+ return m;
+}
+static int
+pritypebefore(struct wbuf *buf, union type ty, int qual)
+{
+ const char *s, *s2;
+ union type chld;
+ int n;
+ switch (ty.t) {
+ case TYVOID: s = "void"; Prim: n = bfmt(buf, "%s", s); return n + priquals(buf, qual);
+ case TYCHAR: s = "char"; goto Prim;
+ case TYSCHAR: s = "signed char"; goto Prim;
+ case TYUCHAR: s = "unsigned char"; goto Prim;
+ case TYSHORT: s = "short"; goto Prim;
+ case TYUSHORT: s = "unsigned short"; goto Prim;
+ case TYINT: s = "int"; goto Prim;
+ case TYUINT: s = "unsigned int"; goto Prim;
+ case TYLONG: s = "long"; goto Prim;
+ case TYULONG: s = "unsigned long"; goto Prim;
+ case TYVLONG: s = "long long"; goto Prim;
+ case TYUVLONG: s = "unsigned long long"; goto Prim;
+ case TYFLOAT: s = "float"; goto Prim;
+ case TYDOUBLE: s = "double"; goto Prim;
+ case TYLDOUBLE:s = "long double"; goto Prim;
+ case TYPTR:
+ chld = typechild(ty);
+ n = pritypebefore(buf, chld, ty.flag & TFCHLDQUAL);
+ //if (chld.t != TYPTR) n += ioputc(buf, ' ');
+ if (chld.t == TYARRAY || chld.t == TYFUNC)
+ n += bputc(buf, '(');
+ n += bputc(buf, '*');
+ n += priquals(buf, qual);
+ return n;
+ case TYARRAY:
+ return pritypebefore(buf, typechild(ty), ty.flag & TFCHLDQUAL);
+ case TYFUNC:
+ return pritypebefore(buf, typedata[ty.dat].ret, 0);
+ case TYSTRUCT:
+ s = "struct";
+ Tagged:
+ n = bfmt(buf, "%s %s", s, (s2 = ttypenames[typedata[ty.dat].id]) ? s2 : "(anonymous)");
+ return n + priquals(buf, qual);
+ case TYUNION:
+ s = "union";
+ goto Tagged;
+ case TYENUM:
+ s = "enum";
+ goto Tagged;
+ default:
+ return bfmt(buf, "?\?%d?",ty.t);
+ }
+}
+
+static int
+pritypeafter(struct wbuf *buf, union type ty, int qual)
+{
+ const struct typedata *td;
+ int n = 0;
+ switch (ty.t) {
+ case TYPTR:
+ if (typechild(ty).t == TYARRAY || typechild(ty).t == TYFUNC)
+ n += bputc(buf, ')');
+ n += pritypeafter(buf, typechild(ty), ty.flag & TFCHLDQUAL);
+ break;
+ case TYARRAY:
+ n += bputc(buf, '[');
+ if (typearrlen(ty))
+ n += bfmt(buf, "%u", typearrlen(ty));
+ n += bputc(buf, ']');
+ n += pritypeafter(buf, typechild(ty), ty.flag & TFCHLDQUAL);
+ break;
+ case TYFUNC:
+ td = &typedata[ty.dat];
+ n += bputc(buf, '(');
+ for (int i = 0; i < td->nmemb; ++i) {
+ n += bfmt(buf, "%tq", td->param[i], tdgetqual(td->quals, i));
+ if (i < td->nmemb - 1 || td->variadic)
+ n += bwriteS(buf, ", ");
+ }
+ if (td->variadic) n += bwriteS(buf, "...");
+ else if (td->nmemb == 0 && !td->kandr) n += bwriteS(buf, "void");
+ n += bwriteS(buf, ")");
+ n += pritypeafter(buf, td->ret, 0);
+ break;
+ }
+ return n;
+}
+
+static int
+fmttype(struct wbuf *buf, union type ty, int qual)
+{
+ int n = pritypebefore(buf, ty, qual);
+ n += pritypeafter(buf, ty, qual);
+ return n;
+}
+
+static int
+putdouble(struct wbuf *buf, double x, vlong prec)
+{
+ int n = 0;
+ if (x < 0) {
+ n += bputc(buf, '-');
+ x = -x;
+ }
+
+ x += 0.5 / prec; // round last decimal
+ if (x >= (double)(-1ULL>>1)) { // out of range?
+ n += bwriteS(buf, "inf");
+ } else {
+ vlong integral = x;
+ vlong fractional = (x - integral)*prec;
+ n += putuint(buf, integral, 10, 0);
+ n += bputc(buf, '.');
+ for (vlong i = prec/10; i > 1; i /= 10) {
+ if (i > fractional) {
+ n += bputc(buf, '0');
+ }
+ }
+ n += putuint(buf, fractional, 10, 0);
+ }
+ return n;
+}
+
+int
+vbfmt(struct wbuf *out, const char *fmt, va_list ap)
+{
+ bool quote, unsgnd, numlong, lower;
+ int base;
+ vlong i;
+ int pad, prec, q;
+ const char *s;
+ void *p;
+ struct token *tok;
+ union type ty;
+ double f;
+ char tmpbuf1[70], tmpbuf2[70];
+ struct wbuf tmp1 = MEMBUF(tmpbuf1, sizeof tmpbuf1);
+ struct wbuf tmp2 = MEMBUF(tmpbuf2, sizeof tmpbuf2);
+ struct wbuf *buf = out;
+ int n = 0, prevn;
+
+ while (*fmt) {
+ buf = out;
+ if (*fmt++ != '%') {
+ n += bputc(buf, fmt[-1]);
+ continue;
+ }
+ if (*fmt == '%') {
+ n += bputc(buf, *fmt++);
+ continue;
+ }
+ fmt += quote = *fmt == '\'';
+ pad = 0;
+ if (aisdigit(*fmt)) { /* left pad */
+ for (; aisdigit(*fmt); ++fmt)
+ pad = pad*10 + *fmt-'0';
+ if (pad) {
+ tmp1.len = 0;
+ buf = &tmp1;
+ }
+ } else if (*fmt == '-') { /* right pad */
+ if (!aisdigit(*++fmt))
+ fmterr("padding amount expected");
+ for (; aisdigit(*fmt); ++fmt)
+ pad = pad*10 - (*fmt-'0');
+ }
+ prec = -1;
+ if (*fmt == '.') {
+ if (!aisdigit(*++fmt))
+ fmterr("precision expected");
+ prec = 0;
+ for (; aisdigit(*fmt); ++fmt)
+ prec = prec*10 + *fmt-'0';
+ }
+ fmt += unsgnd = *fmt == 'u';
+ fmt += numlong = *fmt == 'l';
+ lower = 0;
+ prevn = n;
+ switch (*fmt++) {
+ case 'c': /* character */
+ if (quote) {
+ n += bputc(buf, '\'');
+ n += putquoted(buf, va_arg(ap, int), '\'', -1);
+ n += bputc(buf, '\'');
+ } else {
+ n += bputc(buf, va_arg(ap, int));
+ }
+ break;
+ case 's': /* nullterminated string */
+ s = va_arg(ap, const char *);
+ assert(s && "%s null!");
+ if (quote) {
+ n += bputc(buf, '"');
+ for (; *s; ++s) n += putquoted(buf, *s, '"', s[1]);
+ n += bputc(buf, '"');
+ } else {
+ while (*s) n += bputc(buf, *s++);
+ }
+ break;
+ case 'S': /* string ptr + len */
+ s = va_arg(ap, const char *);
+ i = va_arg(ap, uint);
+ if (quote) {
+ n += bputc(buf, '"');
+ for (; i--; ++s) n += putquoted(buf, *s, '"', i ? s[1] : -1);
+ n += bputc(buf, '"');
+ } else {
+ iowrite(buf, s, i);
+ n += i;
+ }
+ break;
+ case 'd': /* decimal */
+ base = 10;
+ Int:
+ if (base != 10) unsgnd = 1;
+ i = numlong ? va_arg(ap, vlong) : (unsgnd ? va_arg(ap, uint) : (vlong)va_arg(ap, int));
+ tmp2.len = 0;
+ if (quote) {
+ switch (base) {
+ case 2: n += bwriteS(buf, "0b"); break;
+ case 8: n += bwriteS(buf, "0"); break;
+ case 16: n += bwriteS(buf, "0x"); break;
+ }
+ }
+ if (!unsgnd && i < 0) {
+ n += bputc(buf, '-');
+ i = -i;
+ }
+ n += putuint(prec > 0 ? &tmp2 : buf, i, base, lower);
+ if (prec > 0) {
+ int fil = prec - tmp2.len;
+ while (fil-- > 0) n += bputc(buf, '0');
+ iowrite(buf, tmp2.buf, tmp2.len);
+ }
+ break;
+ case 'o': /* octal */
+ base = 8;
+ goto Int;
+ case 'b': /* binary */
+ base = 2;
+ goto Int;
+ case 'x': case 'X': /* hexadecimal */
+ base = 16;
+ lower = fmt[-1] == 'x';
+ goto Int;
+ case 'p': /* pointer */
+ p = va_arg(ap, void *);
+ if (!p && quote) {
+ n += bwriteS(buf, "NULL");
+ } else {
+ n += bwriteS(buf, "0x");
+ tmp2.len = 0;
+ n += putuint(prec > 0 ? &tmp2 : buf, (uvlong)p, 16, 1);
+ if (prec > 0) {
+ int fil = prec - tmp2.len;
+ while (fil-- > 0) n += bputc(buf, '0');
+ iowrite(buf, tmp2.buf, tmp2.len);
+ }
+ }
+ break;
+ case 'f': /* float */
+ f = va_arg(ap, double);
+ n += putdouble(buf, f, prec <= 0 ? 10e6 : prec);
+ break;
+ case 't': /* token/tokentag/type */
+ switch (*fmt++) {
+ case 'k': /* tk token */
+ tok = va_arg(ap, struct token *);
+ Tok:
+ switch (tok->t) {
+ case TKXXX:
+ n += bwriteS(buf, "???");
+ break;
+ case TKNUMLIT:
+ s = (const char *)(getfile(tok->span.sl.file)->p + tok->span.sl.off);
+ if (quote) n += bputc(buf, '`');
+ for (i = tok->span.sl.len; i--; ++s)
+ if (*s != '\\' && *s != '\n') n += bputc(buf, *s);
+ if (quote) n += bputc(buf, '\'');
+ break;
+ case TKSTRLIT:
+ n += bfmt(buf, "%'S", tok->s.p, tok->s.n-1);
+ break;
+ case TKIDENT:
+ n += bfmt(buf, "`%s'", tok->ident);
+ break;
+ case TKEOF:
+ n += bwriteS(buf, "<end-of-file>");
+ break;
+ default:
+ if (quote) n += bputc(buf, '`');
+ if (in_range(tok->t, TKWBEGIN_, TKWEND_)) {
+ n += bfmt(buf, "%s", tok->ident);
+ } else if (aisprint(tok->t)) {
+ n += bputc(buf, tok->t);
+ } else if (tok->t >> 16) {
+ n += bputc(buf, tok->t >> 0);
+ n += bputc(buf, tok->t >> 8);
+ n += bputc(buf, tok->t >> 16);
+ } else {
+ n += bputc(buf, tok->t >> 0);
+ n += bputc(buf, tok->t >> 8);
+ }
+ if (quote) n += bputc(buf, '\'');
+ break;
+ }
+ break;
+ case 't': /* tt token tag */
+ tok = &(struct token) { va_arg(ap, int) };
+ switch (tok->t) {
+ case TKNUMLIT:
+ n += bfmt(buf, "numeric literal");
+ break;
+ case TKSTRLIT:
+ n += bfmt(buf, "string literal");
+ break;
+ case TKIDENT:
+ n += bfmt(buf, "identifier");
+ break;
+ case TKEOF:
+ n += bfmt(buf, "<end-of-file>");
+ break;
+ default:
+ if (tok->t >= TKWBEGIN_ && tok->t <= TKWEND_) {
+ static const char *tab[] = {
+ #define _(kw, c) #kw,
+ #include "keywords.def"
+ #undef _
+ };
+ n += bfmt(buf, "%s", tab[tok->t - TKWBEGIN_]);
+ break;
+ }
+ goto Tok;
+ }
+ break;
+ case 'y': /* ty type */
+ ty = va_arg(ap, union type);
+ n += fmttype(buf, ty, 0);
+ break;
+ case 'q': /* tq qualified type */
+ ty = va_arg(ap, union type);
+ q = va_arg(ap, int);
+ n += fmttype(buf, ty, q);
+ break;
+ default:
+ if (fmt[-1] == ' ' || !aisprint(fmt[-1]))
+ fmterr("expected format specifier");
+ else
+ fmterr("unknown format specifier 't%c'", fmt[-1]);
+ }
+ break;
+ case 'g': /* graphics rendition (color) */
+ if (!ccopt.nocolor) n += bwriteS(buf, "\033[");
+ while (*fmt++ != '.') {
+ if (ccopt.nocolor) continue;
+ n += bputc(buf, fmt[-1]);
+ }
+ if (!ccopt.nocolor) n += bputc(buf, 'm');
+ break;
+ default:
+ if (unsgnd || numlong) {
+ --fmt;
+ base = 10;
+ goto Int;
+ }
+ if (fmt[-1] == ' ' || !aisprint(fmt[-1]))
+ fmterr("expected format specifier");
+ else
+ fmterr("unknown format specifier '%c'", fmt[-1]);
+ }
+ if (pad > 0) { /* left pad */
+ while (pad-- > buf->len)
+ n += bputc(out, ' ');
+ iowrite(out, buf->buf, buf->len);
+ out->err |= buf->err;
+ } else if (pad < 0) { /* right pad */
+ int len = n - prevn;
+ while (pad++ < -len)
+ n += bputc(out, ' ');
+ }
+ }
+ return buf->err ? -1 : n;
+}
+
+int
+bfmt(struct wbuf *buf, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vbfmt(buf, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+static uint pagesiz;
+
+struct memfile
+mapopen(const char **err, const char *path)
+{
+ struct stat stat;
+ int fd = -1;
+ void *p = NULL;
+ struct memfile f = {0};
+ uint mapsiz;
+
+ assert("nullp" && err && path);
+
+ if (!pagesiz) pagesiz = sysconf(_SC_PAGESIZE);
+ *err = NULL;
+
+ if ((fd = open(path, O_RDONLY)) < 0)
+ goto Err;
+ if (fstat(fd, &stat) != 0)
+ goto Err;
+
+ if (!S_ISREG(stat.st_mode)) {
+ uint cap = 0;
+ int ret;
+
+ do {
+ enum { CHUNKSIZ = 1<<16 };
+ if ((cap += CHUNKSIZ) < CHUNKSIZ) {
+ /* overflow */
+ free(p);
+ goto Big;
+ }
+ if (!(f.p = p ? realloc(p, cap) : malloc(cap))) {
+ free(p);
+ goto Err;
+ }
+ p = (void *)f.p;
+ ret = read(fd, p, CHUNKSIZ);
+ if (ret >= 0)
+ f.n += ret;
+ else if (errno != EAGAIN && errno != EWOULDBLOCK)
+ goto Err;
+ } while (ret != 0);
+
+ close(fd);
+ fd = -1;
+ mapsiz = alignup(f.n, pagesiz);
+ if ((f.p = mmap(NULL, mapsiz + pagesiz, PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
+ free(p);
+ goto Err;
+ }
+ memcpy((void *)f.p, p, f.n);
+ free(p);
+ mprotect((void *)f.p, mapsiz + pagesiz, PROT_READ);
+ return f;
+ }
+
+ if (stat.st_size > UINT_MAX) {
+ Big:
+ errno = EFBIG;
+ goto Err;
+ }
+ mapsiz = alignup(stat.st_size, pagesiz);
+ if ((p = mmap(NULL, mapsiz + pagesiz, PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+ goto Err;
+ if (mapsiz > 0 && mmap(p, mapsiz, PROT_READ, MAP_FIXED|MAP_PRIVATE, fd, 0) == MAP_FAILED)
+ goto Err;
+
+ close(fd);
+ f.p = p;
+ f.n = stat.st_size;
+ return f;
+Err:
+ if (fd >= 0) close(fd);
+ if (!*err) *err = strerror(errno);
+ return f;
+}
+
+void
+_assertfmt(const char *file, int line, const char *func, const char *expr)
+{
+ ioflush(&bstdout);
+ efmt("%s:%d: %s: Assertion `%s' failed.\n", file, line, func, expr);
+ ioflush(&bstderr);
+}
+
+void
+mapclose(struct memfile *f)
+{
+ assert(f->p);
+ munmap((void *)f->p, alignup(f->n, pagesiz) + pagesiz);
+ memset(f, 0, sizeof *f);
+}
+
+static struct file {
+ const char *path;
+ struct memfile f;
+ vec_of(uint) lineoffs;
+} files[256];
+static int nfiles;
+
+int
+openfile(const char **err, struct memfile **pf, const char *path)
+{
+ struct file *f;
+
+ assert(nfiles + 1 < arraylength(files) && "too many files");
+ f = &files[nfiles];
+ f->path = path;
+ f->f = mapopen(err, path);
+ if (*err) return -1;
+ vinit(&f->lineoffs, NULL, 50);
+ vpush(&f->lineoffs, 0);
+ *pf = &f->f;
+ return nfiles++;
+}
+
+const char *
+getfilename(int id)
+{
+ assert(id < nfiles);
+ return files[id].path;
+}
+
+struct memfile *
+getfile(int id)
+{
+ assert(id < nfiles);
+ return &files[id].f;
+}
+
+void
+addfileline(int id, uint off)
+{
+ vec_of(uint) *lineoffs;
+
+ assert(id < nfiles);
+ lineoffs = (void *)&files[id].lineoffs;
+ if (lineoffs->n) assert(off > lineoffs->p[lineoffs->n-1]);
+ vpush(lineoffs, off);
+}
+
+void
+getfilepos(int *line, int *col, int id, uint off)
+{
+ uint *offs, n;
+ int l = 0, h, i = 0;
+
+ assert(id < nfiles);
+ offs = files[id].lineoffs.p;
+ n = files[id].lineoffs.n;
+ h = n - 1;
+
+ /* binary search over offsets array */
+ while (l <= h) {
+ i = (l + h) / 2;
+ if (offs[i] < off) l = i + 1;
+ else if (offs[i] > off) h = i - 1;
+ else break;
+ }
+ i -= offs[i] > off;
+ *line = i + 1;
+ *col = off - offs[i] + 1;
+}
+
+void
+closefile(int id)
+{
+ assert(id >= 0 && id < nfiles);
+ mapclose(&files[id].f);
+}
+
+enum diagkind { DGERROR, DGWARN, DGNOTE, };
+
+void
+vdiag(const struct span *span, enum diagkind kind, const char *fmt, va_list ap)
+{
+ static const char *label[] = { "error", "warning", "note" };
+ static const char *color[] = { "%g1;31.", "%g1;35.", "%g1;36." };
+ int line, col;
+ struct memfile *f;
+ const struct span0 *loc;
+
+ if (span) {
+ loc = span->ex.len ? &span->ex : &span->sl;
+ f = getfile(loc->file);
+ getfilepos(&line, &col, loc->file, loc->off);
+ efmt("%s:%d:%d: ", getfilename(loc->file), line, col);
+ }
+ efmt(color[kind]);
+ efmt("%s: %g.", label[kind]);
+ vbfmt(&bstderr, fmt, ap);
+ efmt("\n");
+ if (span) {
+ uint i;
+ int j, nmark;
+ char mark = '^';
+
+ /* find start of line */
+ for (i = loc->off - 1; i + 1 > 0 && f->p[i] != '\n'; --i) ;
+ if (i) ++i;
+
+ nmark = loc->len;
+ while (i < loc->off + loc->len) {
+ int end;
+ int curoff = efmt("%5d | ", line);
+ for (end = 0; f->p[i] != '\n' && i < f->n; ++i, ++end)
+ ioputc(&bstderr, f->p[i]);
+ ++i;
+ ioputc(&bstderr, '\n');
+
+ for (j = 0; j < curoff + col - 1; ++j)
+ ioputc(&bstderr, j == curoff-2 ? '|' : ' ');
+ efmt(color[kind]);
+ j -= curoff;
+ do {
+ ioputc(&bstderr, mark);
+ mark = '~';
+ } while (--nmark && ++j < end);
+ col = 1;
+ ++line;
+ efmt("%g.\n");
+ }
+ ioputc(&bstderr, '\n');
+ }
+
+ if (span && loc == &span->ex && span->sl.len)
+ if (span->ex.file != span->sl.file || !((uint) span->sl.off - span->ex.off < span->ex.len))
+ note(&(struct span){ span->sl }, "expanded from here");
+}
+
+void
+fatal(const struct span *span, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdiag(span, DGERROR, fmt, ap);
+ va_end(ap);
+ efmt("Aborting due to previous error.\n");
+ exit(1);
+}
+
+int nerror;
+
+void
+error(const struct span *span, const char *fmt, ...)
+{
+ va_list ap;
+
+ ++nerror;
+ va_start(ap, fmt);
+ vdiag(span, DGERROR, fmt, ap);
+ va_end(ap);
+}
+
+void
+warn(const struct span *span, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdiag(span, DGWARN, fmt, ap);
+ va_end(ap);
+}
+
+void
+note(const struct span *span, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdiag(span, DGNOTE, fmt, ap);
+ va_end(ap);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */