commit 2f953bf0495054ce6b4d73c3791f271d8079f907
Author: Lou Woell <lou.woell@posteo.de>
Date: Wed, 3 Sep 2025 23:53:51 +0200
Initial Commit
Diffstat:
6 files changed, 401 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,31 @@
+.POSIX:
+.SUFFIXES:
+HARE=hare
+HAREFLAGS=
+
+DESTDIR=
+PREFIX=$(HOME)/.local
+BINDIR=$(PREFIX)/bin
+
+HARE_SOURCES != find . -name '*.ha'
+
+BINS = harehelper
+
+all: $(BINS)
+
+$(BINS): $(HARE_SOURCES)
+ $(HARE) build $(HAREFLAGS) -o $@ cmd/$@/
+
+check:
+ $(HARE) test $(HAREFLAGS)
+
+clean:
+ rm -f $BINS
+
+install:
+ install -Dm755 example-cmd $(DESTDIR)$(BINDIR)/example-cmd
+
+uninstall:
+ rm -f $(DESTDIR)$(BINDIR)/example-cmd
+
+.PHONY: all check clean install uninstall
diff --git a/cmd/harehelper/find.ha b/cmd/harehelper/find.ha
@@ -0,0 +1,100 @@
+use bufio;
+use fmt;
+use fs;
+use getopt;
+use hare::ast;
+use hare::lex;
+use hare::parse;
+use hare::unparse;
+use io;
+use os;
+
+fn find(cmd: getopt::command) void = {
+
+ let id = parse::identstr(cmd.args[0])!;
+ let file = cmd.args[1];
+
+ match(find_uid(id, file)) {
+ case let id_exp: ast::ident =>
+ unparse::ident(
+ os::stdout,
+ id_exp,
+ )!;
+ fmt::println()!;
+ case => void;
+ };
+};
+
+fn list_imports (path: str) []ast::import = {
+ const input = match (os::open(path)) {
+ case let f: io::file =>
+ yield f;
+ case let err: fs::error =>
+ fmt::fatalf("Error reading {}: {}", path, fs::strerror(err));
+ };
+ defer io::close(input)!;
+ let sc = bufio::newscanner(input);
+ defer bufio::finish(&sc);
+ let lexer = lex::init(&sc, path, lex::flag::NONE);
+
+ return parse::imports(&lexer)!;
+};
+
+fn find_uid (id: ast::ident, path: str) (void | ast::ident) = {
+
+ let imports = list_imports(path);
+ defer ast::imports_finish(imports);
+
+ for (let i .. imports) {
+
+ let ident = i.ident;
+ let check = false;
+ let ns = ident_ns(id);
+ let interned = false;
+
+ match(i.bindings){
+ case let a: ast::import_alias =>
+ ident = [a];
+ case let members: ast::import_members =>
+ if(len(id) > 1) continue;
+ for (let s .. members) {
+ check = (s == id[0]);
+ interned = true;
+ break;
+ };
+ case ast::import_wildcard =>
+ // todo check all symbols;
+ void;
+ case void => void;
+ };
+
+ if(!check){
+ check = ast::ident_eq(ns, ident) ||
+ ast::ident_eq(ns, ident_last(ident));
+ };
+
+ if (check) {
+ let id_exp = ast::ident_dup(i.ident);
+ if(len(id) > 1 && !interned){
+ let end = ident_last(id);
+ let id_end = ast::ident_dup(end);
+ append(id_exp, id_end[0])!;
+ };
+ return id_exp;
+ };
+ };
+
+ return void;
+};
+
+fn ident_last(id: ast::ident) ast::ident = {
+ let l = len(id);
+
+ if(l > 1) return id[l - 1 ..] else return id;
+};
+
+fn ident_ns(id: ast::ident) ast::ident = {
+ let l = len(id);
+
+ if(l > 1) return id[..l - 1] else return id;
+};
diff --git a/cmd/harehelper/helper.ha b/cmd/harehelper/helper.ha
@@ -0,0 +1,67 @@
+use bufio;
+use dirs;
+use fmt;
+use getopt;
+use hare::module;
+use io;
+use os;
+use path;
+
+def HAREPATH = "/usr/src/hare/stdlib/:/usr/src/hare/third-party:.";
+
+const help: []getopt::help = [
+ "help with hare",
+ ('h', "show help"),
+ ("list", [
+ "list symbols in package",
+ "identier"
+ ]: []getopt::help ),
+ ("find", [
+ "find module for identifier",
+ "args",
+ ]: []getopt::help),
+ ("list-modules", [
+ "findc module for identifier",
+ ]: []getopt::help),
+];
+
+export fn main () void = {
+
+ const ctx = module::context {
+ harepath = os::tryenv("HAREPATH", HAREPATH),
+ harecache = match (os::getenv("HARECACHE")) {
+ case let s: str =>
+ yield s;
+ case void =>
+ yield dirs::cache("hare");
+ },
+ tags = [],
+ };
+
+ const cmd = getopt::parse(os::args, help...);
+ defer getopt::finish(&cmd);
+
+ match(cmd.subcmd){
+ case void => void;
+ case let c: (str, *getopt::command) =>
+ switch(c.0) {
+ case "find" =>
+ find(*c.1);
+ case "list" =>
+ list(*c.1, &ctx);
+ case "list-modules" =>
+ list_modules(&ctx);
+ case => void;
+ };
+ os::exit(0);
+ };
+
+ for (let (k, v) .. cmd.opts) {
+ switch(k){
+ case 'h' =>
+ getopt::printhelp(os::stdout, "cleandir", help)!;
+ os::exit(0);
+ case => void;
+ };
+ };
+};
diff --git a/cmd/harehelper/list_modules.ha b/cmd/harehelper/list_modules.ha
@@ -0,0 +1,41 @@
+use fmt;
+use hare::module;
+use hare::parse;
+use hare::unparse;
+use os;
+use path;
+use strings;
+
+fn get_modules (ctx: *module::context) []module::module = {
+
+ let res: []module::module = [];
+
+ const harepath = strings::split(ctx.harepath, ":")!;
+ let mod = path::init()!;
+
+ for(let p .. harepath) {
+ path::set(&mod, p)!;
+ let r = module::gather_submodules(ctx, &res, &mod, true);
+
+ match (r) {
+ case let e: hare::module::error =>
+ fmt::println(module::strerror(e))!;
+ case => void;
+ };
+ };
+
+ return res;
+};
+
+fn list_modules (ctx: *module::context) void = {
+ const mods = get_modules(ctx);
+ for (let m &.. mods) {
+ unparse::ident(
+ os::stdout,
+ m.ns,
+ )!;
+ fmt::println()!;
+ module::finish(m);
+ };
+ free(mods);
+};
diff --git a/cmd/harehelper/list_symbols.ha b/cmd/harehelper/list_symbols.ha
@@ -0,0 +1,77 @@
+use bufio;
+use fs;
+use hare::lex;
+use fmt;
+use getopt;
+use hare::ast;
+use hare::module;
+use hare::parse;
+use hare::unparse;
+use io;
+use os;
+
+fn list (cmd: getopt::command, ctx: *module::context) void = {
+
+ let id = parse::identstr(cmd.args[0])!;
+ defer ast::ident_free(id);
+
+ let list = list_symbols(ctx, id);
+ for(let i .. list) {
+ unparse::ident(
+ os::stdout,
+ i,
+ )!;
+ fmt::println()!;
+ ast::ident_free(i);
+ };
+ free(list);
+};
+
+fn list_symbols (ctx: *module::context, id: ast::ident) []ast::ident = {
+
+ let (path, src) = module::find(ctx, id)!;
+
+ let res: []ast::ident = [];
+
+ for(let s .. src.ha) {
+ let decls = scan(s)!;
+ for (let d .. decls) {
+ if(!d.exported) continue;
+ match(d.decl){
+ case let d: []ast::decl_const => void;
+ for (let t .. d) {
+ append(res, ast::ident_dup(t.ident))!;
+ };
+ case let d: []ast::decl_global => void;
+ for (let t .. d) {
+ append(res, ast::ident_dup(t.ident))!;
+ };
+ case let d: []ast::decl_type => void;
+ for (let t .. d) {
+ append(res, ast::ident_dup(t.ident))!;
+ };
+ case let d: ast::decl_func => void;
+ append(res, ast::ident_dup(d.ident))!;
+ case let d: ast::assert_expr => void;
+ };
+ };
+ };
+
+ return res;
+};
+
+fn scan(path: str) ([]ast::decl | parse::error) = {
+ const input = match (os::open(path)) {
+ case let f: io::file =>
+ yield f;
+ case let err: fs::error =>
+ fmt::fatalf("Error reading {}: {}", path, fs::strerror(err));
+ };
+ defer io::close(input)!;
+ let sc = bufio::newscanner(input);
+ defer bufio::finish(&sc);
+ let lexer = lex::init(&sc, path, lex::flag::NONE);
+ let su = parse::subunit(&lexer)?;
+ ast::imports_finish(su.imports);
+ return su.decls;
+};
diff --git a/haredoc.el b/haredoc.el
@@ -0,0 +1,85 @@
+;;; haredoc.el --- Haredoc Integratio in Emacs -*- lexical-binding: t -*-
+
+;; Author: Lou Woell
+;; Version: 0.1
+;; Package-Requires:
+;; Homepage:
+;; Keywords:
+
+;; This file is not part of GNU Emacs
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more detai
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Better emacs integration for hare.
+
+;;; TODO:
+
+;; - make references in docs clickable links
+
+;;; Code:
+
+(require 'ansi-color)
+
+(defvar hare/helperbindir (f-dirname (macroexp-file-name)))
+(defvar hare/helperbin (concat hare/helperbindir "/harehelper"))
+
+(defun hh/exec (strings)
+ (shell-command-to-string (string-join strings " ")))
+
+(defun hare/get-modules ()
+ "Return list of Hare modules."
+ (string-split (hh/exec (list hare/helperbin "list-modules")) "\n"))
+
+(defun hare/read-module (&optional init)
+ (completing-read "Pick Module: " (hare/get-modules) nil nil init))
+
+(defun hare/identifier-at-point ()
+ (let ((id (cond
+ ((use-region-p) ;; Use region or the current word
+ (buffer-substring-no-properties (region-beginning)
+ (region-end)))
+ (t (progn (thing-at-point-looking-at
+ "\\(\\(\\sw\\|\\s_\\)+::\\)*\\(\\sw\\|\\s_\\)+")
+ (string-replace "*" "" (match-string 0)))))))
+ (hh/exec (list hare/helperbin "find" id (buffer-file-name)))))
+
+;; Shows haredoc for the selected or word.
+(defun haredoc (&optional sign)
+ "Run haredoc with the current word under the cursor and display
+the results in a new buffer."
+ (interactive)
+ (let* ((current-word
+ (cond
+ (sign sign)
+ (t (hare/read-module))))
+
+ (command (concat "haredoc -a -F tty " current-word))
+ (output (shell-command-to-string command))
+ (buffer-name "*haredoc*"))
+ (with-output-to-temp-buffer buffer-name
+ (with-current-buffer buffer-name
+ (insert output)
+ (ansi-color-apply-on-region (point-min) (point-max))
+ (haredoc-mode)))))
+
+(defun haredoc-describe-thing-at-point ()
+ (interactive)
+ (haredoc (hare/identifier-at-point)))
+
+(define-derived-mode haredoc-mode help-mode "🐇")
+
+(provide 'haredoc)
+;;; haredoc.el ends here