~yerinalexey/dotfiles

7eeb512c9fe63c70f7ca3f217888fc9d15364b21 — Alexey Yerin 7 months ago 20b0116
scripts: add hfind
2 files changed, 171 insertions(+), 0 deletions(-)

M scripts/.gitignore
A scripts/hfind.ha
M scripts/.gitignore => scripts/.gitignore +1 -0
@@ 1,1 1,2 @@
himitsu-menu
hfind

A scripts/hfind.ha => scripts/hfind.ha +170 -0
@@ 0,0 1,170 @@
use ascii;
use bufio;
use dirs;
use fmt;
use getopt;
use hare::ast;
use hare::lex;
use hare::module;
use hare::parse;
use io;
use os::exec;
use os;
use strings;

export fn main() void = {
	const help: [_]getopt::help = [
		"locate a declaration in a Hare module",
		('a', "consider unexported declarations"),
		('e', "open the file with $EDITOR"),
		"<identifier>",
	];
	const cmd = getopt::parse(os::args, help...);
	defer getopt::finish(&cmd);

	let all = false, edit = false;
	for (let i = 0z; i < len(cmd.opts); i += 1) {
		const (option, _) = cmd.opts[i];
		switch (option) {
		case 'a' =>
			all = true;
		case 'e' =>
			edit = true;
		case => abort();
		};
	};

	if (len(cmd.args) != 1) {
		getopt::printusage(os::stderr, os::args[0], help)!;
		os::exit(os::status::FAILURE);
	};

	match (run(cmd.args[0], all, edit)) {
	case void => void;
	case let err: module::error =>
		fmt::fatal("Error:", module::strerror(err));
	};
};

fn run(id: str, all: bool, edit: bool) (void | module::error) = {
	let ctx = module::context {
		harepath = harepath(),
		harecache = harecache(),
		tags = default_tags(),
	};
	defer strings::freeall(ctx.tags);

	const id = parse::identstr(id)?;
	defer ast::ident_free(id);

	const mod_id = id[..len(id) - 1];
	const decl_id: ast::ident = [ id[len(id) - 1] ];

	const (_, srcset) = module::find(&ctx, mod_id)?;

	static let rbuf: [os::BUFSZ]u8 = [0...];
	let loc = :out {
		for (let i = 0z; i < len(srcset.ha); i += 1) {
			const file = srcset.ha[i];

			const in = os::open(file)?; // BAD ERROR MESSAGES!
			defer io::close(in)!;

			const in = bufio::init(in, rbuf, []);

			const lexer = lex::init(&in, file);
			const subunit = parse::subunit(&lexer)?;
			defer ast::subunit_finish(subunit);

			match (find(subunit.decls, decl_id, all)) {
			case let d: *ast::decl =>
				yield :out, lex::location {
					path = strings::dup(d.start.path),
					line = d.start.line,
					col = d.start.col,
				};
			case null => void;
			};
		};
	};

	match (loc) {
	case let loc: lex::location =>
		if (edit) {
			launch_editor(loc.path, loc.line);
		} else {
			fmt::printfln("{}:{},{}", loc.path,
				loc.line, loc.col)!;
		};
		// Can't use defer because it use-after-frees with never
		free(loc.path);
	case void =>
		fmt::fatal("Nothing found");
	};
};

fn launch_editor(path: str, line: uint) never = {
	const line_plus = fmt::asprintf("+{}", line);
	const editor = os::tryenv("EDITOR", "vis");
	const command = match (exec::cmd(editor, line_plus, path)) {
	case let command: exec::command =>
		yield command;
	case let err: exec::error =>
		fmt::fatal("Failed to open editor:", exec::strerror(err));
	};
	exec::exec(&command);
};

fn find(decls: []ast::decl, id: ast::ident, all: bool) nullable *ast::decl = {
	for (let i = 0z; i < len(decls); i += 1) {
		if (!decls[i].exported && !all) {
			continue;
		};

		match (decls[i].decl) {
		case let c: []ast::decl_const =>
			for (let i = 0z; i < len(c); i += 1) {
				if (ast::ident_eq(id, c[i].ident)) {
					return &decls[i];
				};
			};
		case let g: []ast::decl_global =>
			for (let i = 0z; i < len(g); i += 1) {
				if (ast::ident_eq(id, g[i].ident)) {
					return &decls[i];
				};
			};
		case let t: []ast::decl_type =>
			for (let i = 0z; i < len(t); i += 1) {
				if (ast::ident_eq(id, t[i].ident)) {
					return &decls[i];
				};
			};
		case let f: ast::decl_func =>
			if (ast::ident_eq(id, f.ident)) {
				return &decls[i];
			};
		};
	};
	return null;
};

// Conveniently stolen from cmd/hare/util.ha

fn harepath() str = os::tryenv("HAREPATH", "");

fn harecache() str = {
	match (os::getenv("HARECACHE")) {
	case let s: str =>
		return s;
	case void =>
		return dirs::cache("hare");
	};
};

fn default_tags() []str = {
	let arch = os::machine();
	let platform = ascii::strlower(os::sysname());
	let tags: []str = alloc([strings::dup(arch), platform]);
	return tags;
};