use fmt;
use fs;
use hare::ast;
use hare::lex;
use hare::module;
use hare::parse;
use hare::unparse;
use io;
use net::irc;
use os;
use path;
use strings;
use strio;
fn handle_haredoc(
state: *state,
msg: const *irc::message,
cmd: []str,
) (void | error) = {
if (len(cmd) != 2) {
// TODO: Return error
return;
};
// TODO: Let the user customize the tags with getopt params
const default_harepath = "/usr/share/src/hare/stdlib:/usr/share/src/hare/third-party";
let default_tags: []module::tag = alloc([module::tag {
name = strings::dup("linux"),
mode = module::tag_mode::INCLUSIVE,
}, module::tag {
name = strings::dup(os::machine()),
mode = module::tag_mode::INCLUSIVE,
}]);
defer module::tags_free(default_tags);
let ctx = module::context_init(default_tags, [], default_harepath);
defer module::context_finish(&ctx);
let decls: []ast::decl = [];
defer {
for (let i = 0z; i < len(decls); i += 1) {
ast::decl_free(decls[i]);
};
free(decls);
};
const id = parse::identstr(cmd[1])?;
defer ast::ident_free(id);
let decl = "";
let dirname = if (len(id) < 2) id else id[..len(id) - 1];
const version = match (module::lookup(&ctx, id)) {
case ver: module::version =>
let path = module::identpath(id);
defer free(path);
let link = fmt::asprintf("{}: https://docs.harelang.org/{}",
cmd[1], path);
defer free(link);
irc::privmsg(state.conn, link, msg.params[0])!;
return;
case err: module::error =>
let ver = module::lookup(&ctx, dirname)?;
assert(len(id) >= 2);
decl = id[len(id) - 1];
yield ver;
};
let path = module::identpath(dirname);
defer free(path);
for (let i = 0z; i < len(version.inputs); i += 1) {
const in = version.inputs[i];
const ext = path::extension(in.path);
if (ext.1 != ".ha") {
continue;
};
append(decls, scan(in.path)?.decls...);
};
let selected: nullable *ast::decl = null;
for (let i = 0z; i < len(decls); i += 1) {
if (has_decl(decls[i], decl)) {
selected = &decls[i];
};
};
let selected = match (selected) {
case null =>
let resp = fmt::asprintf("Could not find {}::{}",
unparse::identstr(dirname), decl);
defer free(resp);
irc::privmsg(state.conn, resp, msg.params[0])!;
return;
case d: *ast::decl =>
yield d;
};
const buf = strio::dynamic();
unparse_hare(buf, *selected)!;
fmt::fprintf(buf, " // https://docs.harelang.org/{}#{}", path, decl)!;
const resp = strio::finish(buf);
defer free(resp);
irc::privmsg(state.conn, resp, msg.params[0])!;
};
fn scan(path: str) (ast::subunit | error) = {
const input = match (os::open(path)) {
case f: io::file =>
yield f;
case err: fs::error =>
fmt::fatal("Error reading {}: {}", path, fs::strerror(err));
};
defer io::close(input);
const lexer = lex::init(input, path, lex::flags::COMMENTS);
return parse::subunit(&lexer)?;
};
fn has_decl(decl: ast::decl, name: str) bool = {
if (!decl.exported) {
return false;
};
match (decl.decl) {
case d: []ast::decl_const =>
for (let i = 0z; i < len(d); i += 1) {
if (len(d[i].ident) == 1 && d[i].ident[0] == name) {
return true;
};
};
case d: ast::decl_func =>
return len(d.ident) == 1 && d.ident[0] == name;
case d: []ast::decl_global =>
for (let i = 0z; i < len(d); i += 1) {
if (len(d[i].ident) == 1 && d[i].ident[0] == name) {
return true;
};
};
case d: []ast::decl_type =>
for (let i = 0z; i < len(d); i += 1) {
if (len(d[i].ident) == 1 && d[i].ident[0] == name) {
return true;
};
};
};
return false;
};
// Forked from haredoc's hare output
fn unparse_hare(out: io::handle, d: ast::decl) (size | io::error) = {
let n = 0z;
match (d.decl) {
case g: []ast::decl_global =>
n += fmt::fprint(out,
if (g[0].is_const) "const " else "let ")?;
for (let i = 0z; i < len(g); i += 1) {
if (len(g[i].symbol) != 0) {
n += fmt::fprintf(out,
"@symbol(\"{}\") ", g[i].symbol)?;
};
n += unparse::ident(out, g[i].ident)?;
n += fmt::fprint(out, ": ")?;
n += unparse::_type(out, 0, g[i]._type)?;
if (i + 1 < len(g)) {
n += fmt::fprint(out, ", ")?;
};
};
case t: []ast::decl_type =>
n += fmt::fprint(out, "type ")?;
for (let i = 0z; i < len(t); i += 1) {
n += unparse::ident(out, t[i].ident)?;
n += fmt::fprint(out, " = ")?;
match (t[i]._type.repr) {
case a: ast::alias_type =>
if (a.unwrap) {
n += fmt::fprintf(out, "...")?;
};
n += unparse::ident(out, a.ident)?;
case b: ast::builtin_type =>
n += fmt::fprint(out, unparse::builtin_type(b))?;
case e: ast::enum_type =>
n += fmt::fprint(out, "enum")?;
case (ast::func_type | ast::list_type) =>
n += unparse::_type(out, 0, t[i]._type)?;
case =>
n += fmt::fprint(out, "[...]")?;
};
if (i + 1 < len(t)) {
n += fmt::fprint(out, ", ")?;
};
};
case c: []ast::decl_const =>
n += fmt::fprint(out, "def ")?;
for (let i = 0z; i < len(c); i += 1) {
n += unparse::ident(out, c[i].ident)?;
n += fmt::fprint(out, ": ")?;
n += unparse::_type(out, 0, c[i]._type)?;
if (i + 1 < len(c)) {
n += fmt::fprint(out, ", ")?;
};
};
case f: ast::decl_func =>
n += fmt::fprint(out, switch (f.attrs) {
case ast::fndecl_attrs::NONE =>
yield "";
case ast::fndecl_attrs::FINI =>
yield "@fini ";
case ast::fndecl_attrs::INIT =>
yield "@init ";
case ast::fndecl_attrs::TEST =>
yield "@test ";
})?;
let p = f.prototype.repr as ast::func_type;
if (p.attrs & ast::func_attrs::NORETURN != 0) {
n += fmt::fprint(out, "@noreturn ")?;
};
if (len(f.symbol) != 0) {
n += fmt::fprintf(out, "@symbol(\"{}\") ",
f.symbol)?;
};
n += fmt::fprint(out, "fn ")?;
n += unparse::ident(out, f.ident)?;
n += prototype_hare(out, 0,
f.prototype.repr as ast::func_type)?;
};
n += fmt::fprint(out, ";")?;
return n;
};
fn prototype_hare(
out: io::handle,
indent: size,
t: ast::func_type,
) (size | io::error) = {
let n = 0z;
n += fmt::fprint(out, "(")?;
for (let i = 0z; i < len(t.params); i += 1) {
let param = t.params[i];
n += fmt::fprintf(out, "{}: ",
if (len(param.name) == 0) "_" else param.name)?;
n += unparse::_type(out, indent, *param._type)?;
if (i + 1 == len(t.params)
&& t.variadism == ast::variadism::HARE) {
n += fmt::fprintf(out, "...")?;
};
if (i + 1 < len(t.params)) {
n += fmt::fprint(out, ", ")?;
};
};
if (t.variadism == ast::variadism::C) {
n += fmt::fprint(out, ", ...")?;
};
n += fmt::fprint(out, ") ")?;
n += unparse::_type(out, indent, *t.result)?;
return n;
};