@ 261101.7
current
// walk.c parses the source and writes its named nodes in one call from Go.
// The Go binding crosses cgo for each node it is asked about, about ten
// times a node, anonymous nodes included, which took longer than the
// parse itself; here the walk never leaves C.

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// The declarations below are copied from tree-sitter's api.h, as
// go-tree-sitter v0.25.0 ships it; cgo has no path to that header in the
// module cache. The layouts of TSPoint, TSNode and TSTreeCursor must
// match it, since they pass by value.
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
typedef struct TSParser TSParser;
typedef struct TSTree TSTree;

typedef struct TSPoint {
	uint32_t row;
	uint32_t column;
} TSPoint;

typedef struct TSNode {
	uint32_t context[4];
	const void *id;
	const TSTree *tree;
} TSNode;

typedef struct TSTreeCursor {
	const void *tree;
	const void *id;
	uint32_t context[3];
} TSTreeCursor;

TSParser *ts_parser_new(void);
void ts_parser_delete(TSParser *self);
bool ts_parser_set_language(TSParser *self, const TSLanguage *language);
TSTree *ts_parser_parse_string(TSParser *self, const TSTree *old_tree, const char *string, uint32_t length);
void ts_tree_delete(TSTree *self);
TSNode ts_tree_root_node(const TSTree *self);
TSSymbol ts_node_symbol(TSNode self);
TSPoint ts_node_start_point(TSNode self);
TSPoint ts_node_end_point(TSNode self);
bool ts_node_is_named(TSNode self);
bool ts_node_is_missing(TSNode self);
TSTreeCursor ts_tree_cursor_new(TSNode node);
void ts_tree_cursor_delete(TSTreeCursor *self);
TSNode ts_tree_cursor_current_node(const TSTreeCursor *self);
TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *self);
bool ts_tree_cursor_goto_parent(TSTreeCursor *self);
bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *self);
bool ts_tree_cursor_goto_first_child(TSTreeCursor *self);
const char *ts_language_symbol_name(const TSLanguage *self, TSSymbol symbol);
const char *ts_language_field_name_for_id(const TSLanguage *self, TSFieldId id);

// out buffers the rows for stdout. err holds the first failed write's
// errno, after which nothing more is written.
struct out {
	char buf[1 << 16];
	size_t n;
	int err;
};

static void flush(struct out *o) {
	size_t done = 0;
	while (done < o->n && o->err == 0) {
		ssize_t w = write(1, o->buf + done, o->n - done);
		if (w < 0) {
			if (errno != EINTR) {
				o->err = errno;
			}
			continue;
		}
		done += (size_t)w;
	}
	o->n = 0;
}

static void put(struct out *o, const char *s, size_t len) {
	if (o->n + len > sizeof o->buf) {
		flush(o);
	}
	// A kind longer than the buffer is not in any grammar, but writes
	// whole all the same.
	if (len > sizeof o->buf) {
		memcpy(o->buf, s, sizeof o->buf);
		o->n = sizeof o->buf;
		flush(o);
		put(o, s + sizeof o->buf, len - sizeof o->buf);
		return;
	}
	memcpy(o->buf + o->n, s, len);
	o->n += len;
}

static void putnum(struct out *o, uint64_t v) {
	char d[20];
	int i = sizeof d;
	do {
		d[--i] = '0' + v % 10;
		v /= 10;
	} while (v > 0);
	put(o, d + i, sizeof d - i);
}

static void puttab(struct out *o) {
	put(o, "\t", 1);
}

// syntax_tree parses src of len bytes by lang and writes the named nodes
// in preorder to stdout, one per line, as main.go's comment describes.
// Anonymous nodes, eg punctuation and keywords, are left out, but their
// named descendants keep their place under the nearest named ancestor.
// It returns 0, EINVAL when the grammar does not load, EIO when the
// parser gives no tree, ENOMEM, or the errno of a failed write.
int syntax_tree(const TSLanguage *lang, const char *src, uint32_t len) {
	TSParser *p = ts_parser_new();
	if (!ts_parser_set_language(p, lang)) {
		ts_parser_delete(p);
		return EINVAL;
	}
	TSTree *tree = ts_parser_parse_string(p, NULL, src, len);
	ts_parser_delete(p);
	if (tree == NULL) {
		return EIO;
	}
	struct out *o = calloc(1, sizeof *o);
	// named holds the cursor depths of the shown nodes above the cursor;
	// a row's depth is how many there are.
	size_t cap = 64, top = 0;
	uint32_t *named = malloc(cap * sizeof *named);
	if (o == NULL || named == NULL) {
		free(o);
		free(named);
		ts_tree_delete(tree);
		return ENOMEM;
	}
	int ret = 0;
	TSTreeCursor cur = ts_tree_cursor_new(ts_tree_root_node(tree));
	uint32_t depth = 0;
	for (;;) {
		TSNode n = ts_tree_cursor_current_node(&cur);
		bool missing = ts_node_is_missing(n);
		bool shown = missing || ts_node_is_named(n);
		if (shown) {
			const char *kind = ts_language_symbol_name(lang, ts_node_symbol(n));
			const char *field = ts_language_field_name_for_id(lang, ts_tree_cursor_current_field_id(&cur));
			TSPoint s = ts_node_start_point(n), e = ts_node_end_point(n);
			putnum(o, top);
			puttab(o);
			if (missing) {
				put(o, "MISSING ", 8);
			}
			if (kind != NULL) {
				put(o, kind, strlen(kind));
			}
			puttab(o);
			if (field != NULL) {
				put(o, field, strlen(field));
			}
			puttab(o);
			putnum(o, s.row);
			puttab(o);
			putnum(o, s.column);
			puttab(o);
			putnum(o, e.row);
			puttab(o);
			putnum(o, e.column);
			put(o, "\n", 1);
			if (o->err != 0) {
				break;
			}
		}
		if (ts_tree_cursor_goto_first_child(&cur)) {
			if (shown) {
				if (top == cap) {
					uint32_t *more = realloc(named, 2 * cap * sizeof *named);
					if (more == NULL) {
						ret = ENOMEM;
						break;
					}
					named = more;
					cap *= 2;
				}
				named[top++] = depth;
			}
			depth++;
			continue;
		}
		bool done = false;
		while (!ts_tree_cursor_goto_next_sibling(&cur)) {
			if (!ts_tree_cursor_goto_parent(&cur)) {
				done = true;
				break;
			}
			depth--;
			if (top > 0 && named[top - 1] == depth) {
				top--;
			}
		}
		if (done) {
			break;
		}
	}
	flush(o);
	if (ret == 0) {
		ret = o->err;
	}
	ts_tree_cursor_delete(&cur);
	ts_tree_delete(tree);
	free(named);
	free(o);
	return ret;
}