hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

main.ha (12323B)


      1 // SPDX-License-Identifier: GPL-3.0-only
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bufio;
      5 use cmd::haredoc::doc;
      6 use fmt;
      7 use fs;
      8 use getopt;
      9 use hare::ast;
     10 use hare::lex;
     11 use hare::module;
     12 use hare::parse;
     13 use hare::unparse;
     14 use io;
     15 use memio;
     16 use os;
     17 use os::exec;
     18 use path;
     19 use strconv;
     20 use strings;
     21 
     22 const help: []getopt::help = [
     23 	"reads and formats Hare documentation",
     24 	('a', "show undocumented members (only applies to -Ftty)"),
     25 	('t', "disable HTML template (requires postprocessing)"),
     26 	('F', "format", "specify output format (tty or html)"),
     27 	('T', "tagset", "set/unset build tags"),
     28 	"[identifier|path]",
     29 ];
     30 
     31 export fn main() void = {
     32 	const cmd = getopt::parse(os::args, help...);
     33 	defer getopt::finish(&cmd);
     34 	match (doc(os::args[0], &cmd)) {
     35 	case void => void;
     36 	case let e: doc::error =>
     37 		fmt::fatal(doc::strerror(e));
     38 	case let e: exec::error =>
     39 		fmt::fatal(exec::strerror(e));
     40 	case let e: fs::error =>
     41 		fmt::fatal(fs::strerror(e));
     42 	case let e: io::error =>
     43 		fmt::fatal(io::strerror(e));
     44 	case let e: module::error =>
     45 		fmt::fatal(module::strerror(e));
     46 	case let e: path::error =>
     47 		fmt::fatal(path::strerror(e));
     48 	case let e: parse::error =>
     49 		fmt::fatal(parse::strerror(e));
     50 	case let e: strconv::error =>
     51 		fmt::fatal(strconv::strerror(e));
     52 	};
     53 };
     54 
     55 fn doc(name: str, cmd: *getopt::command) (void | error) = {
     56 	let html = false;
     57 	let template = true;
     58 	let show_undocumented = false;
     59 	let tags: []str = default_tags();
     60 	defer free(tags);
     61 
     62 	for (let (k, v) .. cmd.opts) {
     63 		switch (k) {
     64 		case 'F' =>
     65 			switch (v) {
     66 			case "tty" =>
     67 				html = false;
     68 			case "html" =>
     69 				html = true;
     70 			case =>
     71 				fmt::fatal("Invalid format", v);
     72 			};
     73 		case 'T' =>
     74 			merge_tags(&tags, v)?;
     75 		case 't' =>
     76 			template = false;
     77 		case 'a' =>
     78 			show_undocumented = true;
     79 		case => abort();
     80 		};
     81 	};
     82 
     83 	if (show_undocumented && html) {
     84 		fmt::fatal("Option -a must be used only with -Ftty");
     85 	};
     86 
     87 	if (len(cmd.args) > 1) {
     88 		getopt::printusage(os::stderr, os::args[0], help)!;
     89 		os::exit(os::status::FAILURE);
     90 	};
     91 
     92 	let ctx = module::context {
     93 		harepath = harepath(),
     94 		harecache = harecache(),
     95 		tags = tags,
     96 	};
     97 
     98 	let declpath = "";
     99 	defer free(declpath);
    100 	let declsrcs = module::srcset { ... };
    101 	defer module::finish_srcset(&declsrcs);
    102 	let modpath = "";
    103 	defer free(modpath);
    104 	let modsrcs = module::srcset { ... };
    105 	defer module::finish_srcset(&modsrcs);
    106 	let id: ast::ident = [];
    107 	defer free(id);
    108 
    109 	if (len(cmd.args) == 0) {
    110 		let (p, s) = module::find(&ctx, []: ast::ident)?;
    111 		modpath = strings::dup(p);
    112 		modsrcs = s;
    113 	} else match (parseident(cmd.args[0])) {
    114 	case let ident: (ast::ident, bool) =>
    115 		id = ident.0;
    116 		const trailing = ident.1;
    117 		if (!trailing) {
    118 			// check if it's an ident inside a module
    119 			match (module::find(&ctx, id[..len(id)-1])) {
    120 			case let s: (str, module::srcset) =>
    121 				declpath = strings::dup(s.0);
    122 				declsrcs = s.1;
    123 			case let e: module::error =>
    124 				module::finish_error(e);
    125 			};
    126 		};
    127 		// check if it's a module
    128 		match (module::find(&ctx, id)) {
    129 		case let s: (str, module::srcset) =>
    130 			modpath = strings::dup(s.0);
    131 			modsrcs = s.1;
    132 		case let e: module::error =>
    133 			module::finish_error(e);
    134 			if (declpath == "") {
    135 				const id = unparse::identstr(id);
    136 				fmt::fatalf("Could not find {}{}", id,
    137 					if (trailing) "::" else "");
    138 			};
    139 		};
    140 	case void =>
    141 		let buf = path::init(cmd.args[0])?;
    142 		let (p, s) = module::find(&ctx, &buf)?;
    143 		modpath = strings::dup(p);
    144 		modsrcs = s;
    145 	};
    146 
    147 	let decls: []ast::decl = [];
    148 	defer {
    149 		for (let decl .. decls) {
    150 			ast::decl_finish(decl);
    151 		};
    152 		free(decls);
    153 	};
    154 
    155 	let parse_errs: []lex::syntax = [];
    156 	defer {
    157 		for (const err .. parse_errs) {
    158 			free(err.1);
    159 		};
    160 		free(parse_errs);
    161 	};
    162 
    163 	if (declpath != "") {
    164 		for (let ha .. declsrcs.ha) {
    165 			let d = match (doc::scan(ha)) {
    166 			case let d: []ast::decl =>
    167 				yield d;
    168 			case let err: parse::error =>
    169 				if (html) {
    170 					return err;
    171 				};
    172 				match (err) {
    173 				case let err: lex::syntax =>
    174 					const msg = strings::dup(err.1);
    175 					append(parse_errs, (err.0, msg))!;
    176 					continue;
    177 				case =>
    178 					return err;
    179 				};
    180 			};
    181 			defer free(d);
    182 			append(decls, d...)!;
    183 		};
    184 
    185 		let matching: []ast::decl = [];
    186 		let notmatching: []ast::decl = [];
    187 
    188 		for (let decl .. decls) {
    189 			if (has_decl(decl, id[len(id) - 1])) {
    190 				append(matching, decl)!;
    191 			} else {
    192 				append(notmatching, decl)!;
    193 			};
    194 		};
    195 		get_init_funcs(&matching, &notmatching);
    196 
    197 		for (let decl .. notmatching) {
    198 			ast::decl_finish(decl);
    199 		};
    200 		free(notmatching);
    201 		free(decls);
    202 		decls = matching;
    203 
    204 		if (len(matching) == 0) {
    205 			if (modpath == "") {
    206 				const id = unparse::identstr(id);
    207 				fmt::fatalf("Could not find {}", id);
    208 			};
    209 		} else {
    210 			show_undocumented = true;
    211 		};
    212 	};
    213 
    214 	let readme: (io::file | void) = void;
    215 	defer match (readme) {
    216 	case void => void;
    217 	case let f: io::file =>
    218 		io::close(f)!;
    219 	};
    220 
    221 	const ambiguous = modpath != "" && len(decls) > 0;
    222 
    223 	if (len(decls) == 0) :nodecls {
    224 		for (let ha .. modsrcs.ha) {
    225 			let d = match (doc::scan(ha)) {
    226 			case let d: []ast::decl =>
    227 				yield d;
    228 			case let err: parse::error =>
    229 				if (html) {
    230 					return err;
    231 				};
    232 				match (err) {
    233 				case let err: lex::syntax =>
    234 					const msg = strings::dup(err.1);
    235 					append(parse_errs, (err.0, msg))!;
    236 					continue;
    237 				case =>
    238 					return err;
    239 				};
    240 			};
    241 			defer free(d);
    242 			append(decls, d...)!;
    243 		};
    244 
    245 		const rpath = match (path::init(modpath, "README")) {
    246 		case let buf: path::path =>
    247 			yield buf;
    248 		case let err: path::error =>
    249 			assert(err is path::too_long);
    250 			yield :nodecls;
    251 		};
    252 		match (os::open(path::string(&rpath))) {
    253 		case let f: io::file =>
    254 			readme = f;
    255 		case fs::error => void;
    256 		};
    257 	};
    258 
    259 	const submods: []str = if (!ambiguous && modpath != "") {
    260 		yield match (doc::submodules(modpath, show_undocumented)) {
    261 		case let s: []str =>
    262 			yield s;
    263 		case doc::error =>
    264 			yield [];
    265 		};
    266 	} else [];
    267 	const srcs = if (!ambiguous && modpath != "") modsrcs else declsrcs;
    268 	const summary = doc::sort_decls(decls);
    269 	defer doc::finish_summary(summary);
    270 	const ctx = doc::context {
    271 		mctx = &ctx,
    272 		ident = id,
    273 		tags = tags,
    274 		ambiguous = ambiguous,
    275 		parse_errs = parse_errs,
    276 		srcs = srcs,
    277 		submods = submods,
    278 		summary = summary,
    279 		template = template,
    280 		readme = readme,
    281 		show_undocumented = show_undocumented,
    282 		out = os::stdout,
    283 		pager = void,
    284 	};
    285 
    286 	const ret = if (html) {
    287 		yield doc::emit_html(&ctx);
    288 	} else {
    289 		ctx.out = init_tty(&ctx);
    290 		yield doc::emit_tty(&ctx);
    291 	};
    292 
    293 	io::close(ctx.out)!;
    294 	match (ctx.pager) {
    295 	case void => void;
    296 	case let proc: exec::process =>
    297 		exec::wait(&proc)!;
    298 	};
    299 
    300 	// TODO: remove ? (harec bug workaround)
    301 	return ret?;
    302 };
    303 
    304 // Nearly identical to parse::identstr, except alphanumeric lexical tokens are
    305 // converted to strings and there must be no trailing tokens that don't belong
    306 // to the ident in the string. For example, this function will parse `rt::abort`
    307 // as a valid identifier.
    308 fn parseident(in: str) ((ast::ident, bool) | void) = {
    309 	let buf = memio::fixed(strings::toutf8(in));
    310 	let sc = bufio::newscanner(&buf);
    311 	defer bufio::finish(&sc);
    312 	let lexer = lex::init(&sc, "<string>");
    313 	let success = false;
    314 	let ident: ast::ident = [];
    315 	defer if (!success) ast::ident_free(ident);
    316 	let trailing = false;
    317 	let z = 0z;
    318 	for (true) {
    319 		const tok = lex::lex(&lexer)!;
    320 		const name = if (tok.0 == lex::ltok::NAME) {
    321 			yield tok.1 as str;
    322 		} else if (tok.0 < lex::ltok::LAST_KEYWORD) {
    323 			yield strings::dup(lex::tokstr(tok));
    324 		} else if (tok.0 == lex::ltok::EOF && len(ident) > 0) {
    325 			trailing = true;
    326 			break;
    327 		} else {
    328 			lex::unlex(&lexer, tok);
    329 			return;
    330 		};
    331 		append(ident, name)!;
    332 		z += len(name);
    333 		const tok = lex::lex(&lexer)!;
    334 		switch (tok.0) {
    335 		case lex::ltok::EOF =>
    336 			break;
    337 		case lex::ltok::DOUBLE_COLON =>
    338 			z += 1;
    339 		case =>
    340 			lex::unlex(&lexer, tok);
    341 			return;
    342 		};
    343 	};
    344 	if (z > ast::IDENT_MAX) {
    345 		return;
    346 	};
    347 	success = true;
    348 	return (ident, trailing);
    349 };
    350 
    351 fn init_tty(ctx: *doc::context) io::handle = {
    352 	const pager = match (os::getenv("PAGER")) {
    353 	case let name: str =>
    354 		yield match (exec::cmd(name)) {
    355 		case let cmd: exec::command =>
    356 			yield cmd;
    357 		case exec::error =>
    358 			return os::stdout;
    359 		};
    360 	case void =>
    361 		yield match (exec::cmd("less", "-R")) {
    362 		case let cmd: exec::command =>
    363 			yield cmd;
    364 		case exec::error =>
    365 			yield match (exec::cmd("more", "-R")) {
    366 			case let cmd: exec::command =>
    367 				yield cmd;
    368 			case exec::error =>
    369 				return os::stdout;
    370 			};
    371 		};
    372 	};
    373 
    374 	const pipe = exec::pipe();
    375 	defer io::close(pipe.0)!;
    376 	exec::addfile(&pager, os::stdin_file, pipe.0);
    377 	// Get raw flag in if possible
    378 	exec::setenv(&pager, "LESS", os::tryenv("LESS", "FRX"))!;
    379 	exec::setenv(&pager, "MORE", os::tryenv("MORE", "R"))!;
    380 	ctx.pager = exec::start(&pager)!;
    381 	return pipe.1;
    382 };
    383 
    384 fn has_decl(decl: ast::decl, name: str) bool = {
    385 	if (!decl.exported) {
    386 		return false;
    387 	};
    388 
    389 	match (decl.decl) {
    390 	case let consts: []ast::decl_const =>
    391 		for (let d .. consts) {
    392 			if (len(d.ident) == 1 && d.ident[0] == name) {
    393 				return true;
    394 			};
    395 		};
    396 	case let d: ast::decl_func =>
    397 		if (len(d.ident) == 1 && d.ident[0] == name) {
    398 			return true;
    399 		};
    400 		let tok = strings::rtokenize(d.symbol, ".");
    401 		match (strings::next_token(&tok)) {
    402 		case done => void;
    403 		case let s: str =>
    404 			return s == name;
    405 		};
    406 	case let globals: []ast::decl_global =>
    407 		for (let d .. globals) {
    408 			if (len(d.ident) == 1 && d.ident[0] == name) {
    409 				return true;
    410 			};
    411 			let tok = strings::rtokenize(d.symbol, ".");
    412 			match (strings::next_token(&tok)) {
    413 			case done => void;
    414 			case let s: str =>
    415 				return s == name;
    416 			};
    417 		};
    418 	case let types: []ast::decl_type =>
    419 		for (let d .. types) {
    420 			if (len(d.ident) == 1 && d.ident[0] == name) {
    421 				return true;
    422 			};
    423 		};
    424 	case ast::assert_expr => void;
    425 	};
    426 	return false;
    427 };
    428 
    429 @test fn parseident() void = {
    430 	let (ident, trailing) = parseident("hare::lex") as (ast::ident, bool);
    431 	defer ast::ident_free(ident);
    432 	assert(ast::ident_eq(ident, ["hare", "lex"]));
    433 	assert(!trailing);
    434 
    435 	let (ident, trailing) = parseident("rt::abort") as (ast::ident, bool);
    436 	defer ast::ident_free(ident);
    437 	assert(ast::ident_eq(ident, ["rt", "abort"]));
    438 	assert(!trailing);
    439 
    440 	let (ident, trailing) = parseident("foo::bar::") as (ast::ident, bool);
    441 	defer ast::ident_free(ident);
    442 	assert(ast::ident_eq(ident, ["foo", "bar"]));
    443 	assert(trailing);
    444 	assert(parseident("strings::dup*{}&@") is void);
    445 	assert(parseident("") is void);
    446 	assert(parseident("::") is void);
    447 };
    448 
    449 fn get_init_funcs(matching: *[]ast::decl, notmatching: *[]ast::decl) void = {
    450 	if (len(matching) != 1) {
    451 		return;
    452 	};
    453 	let ident = match (matching[0].decl) {
    454 	case let d: []ast::decl_type =>
    455 		if (len(d) != 1 || len(d[0].ident) != 1) {
    456 			return;
    457 		};
    458 		if (d[0]._type.flags & ast::type_flag::ERROR != 0) {
    459 			return;
    460 		};
    461 		match (d[0]._type.repr) {
    462 		case let repr: ast::builtin_type =>
    463 			if (repr == ast::builtin_type::VOID) {
    464 				return;
    465 			};
    466 		case => void;
    467 		};
    468 		yield d[0].ident;
    469 	case =>
    470 		return;
    471 	};
    472 
    473 	for (let i = 0z; i < len(notmatching); i += 1) {
    474 		let _type = match (notmatching[i].decl) {
    475 		case let d: []ast::decl_const =>
    476 			yield match (d[0]._type) {
    477 			case let t: *ast::_type =>
    478 				yield t;
    479 			case null =>
    480 				continue;
    481 			};
    482 		case let d: []ast::decl_global =>
    483 			yield match (d[0]._type) {
    484 			case let t: *ast::_type =>
    485 				yield t;
    486 			case null =>
    487 				continue;
    488 			};
    489 		case let d: ast::decl_func =>
    490 			let _type = d.prototype.repr as ast::func_type;
    491 			yield _type.result;
    492 		case =>
    493 			continue;
    494 		};
    495 
    496 		if (is_init_type(ident, _type)) {
    497 			append(matching, notmatching[i])!;
    498 			delete(notmatching[i]);
    499 			i -= 1;
    500 		};
    501 	};
    502 };
    503 
    504 fn is_init_type(ident: ast::ident, _type: *ast::_type) bool = {
    505 	let type_ident = match (_type.repr) {
    506 	case let repr: ast::alias_type =>
    507 		yield repr.ident;
    508 	case let repr: ast::list_type =>
    509 		if (!(repr.length is ast::len_slice)) {
    510 			return false;
    511 		};
    512 		yield match (repr.members.repr) {
    513 		case let repr: ast::alias_type =>
    514 			yield repr.ident;
    515 		case =>
    516 			return false;
    517 		};
    518 	case let repr: ast::pointer_type =>
    519 		yield match (repr.referent.repr) {
    520 		case let repr: ast::alias_type =>
    521 			yield repr.ident;
    522 		case =>
    523 			return false;
    524 		};
    525 	case let repr: ast::tagged_type =>
    526 		for (let t .. repr) {
    527 			if (is_init_type(ident, t)) {
    528 				return true;
    529 			};
    530 		};
    531 		return false;
    532 	case =>
    533 		return false;
    534 	};
    535 
    536 	return ast::ident_eq(ident, type_ident);
    537 };