hare

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

deps.ha (5096B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bufio;
      5 use fmt;
      6 use fs;
      7 use hare::ast;
      8 use hare::lex;
      9 use hare::parse;
     10 use hare::unparse;
     11 use io;
     12 use memio;
     13 use os;
     14 use path;
     15 use sort;
     16 use strings;
     17 
     18 // A hare module.
     19 export type module = struct {
     20 	name: str,
     21 	ns: ast::ident,
     22 	path: str,
     23 	srcs: srcset,
     24 	// [](Index to the module, Identifier of of the module)
     25 	deps: [](size, ast::ident),
     26 };
     27 
     28 // Get the list of dependencies referred to by a set of source files.
     29 // The list will be sorted alphabetically and deduplicated.
     30 export fn parse_deps(files: str...) ([]ast::ident | error) = {
     31 	let deps: []ast::ident = [];
     32 	for (let file .. files) {
     33 		let handle = match (os::open(file)) {
     34 		case let f: io::file =>
     35 			yield f;
     36 		case let e: fs::error =>
     37 			return attach(strings::dup(file), e);
     38 		};
     39 		defer io::close(handle)!;
     40 
     41 		let sc = bufio::newscanner(handle);
     42 		defer bufio::finish(&sc);
     43 		let lexer = lex::init(&sc, file);
     44 		let imports = parse::imports(&lexer)?;
     45 		defer ast::imports_finish(imports);
     46 
     47 		// dedupe + insertion sort
     48 		for (let import &.. imports) {
     49 			let id = import.ident;
     50 			let idx = sort::rbisect(deps, size(ast::ident), &id, &idcmp);
     51 			if (idx == 0 || idcmp(&deps[idx - 1], &id) != 0) {
     52 				insert(deps[idx], ast::ident_dup(id));
     53 			};
     54 		};
     55 	};
     56 	return deps;
     57 };
     58 
     59 fn idcmp(a: const *opaque, b: const *opaque) int = {
     60 	const a = a: const *ast::ident, b = b: const *ast::ident;
     61 	for (let i = 0z; i < len(a) && i < len(b); i += 1) {
     62 		let cmp = strings::compare(a[i], b[i]);
     63 		if (cmp != 0) {
     64 			return cmp;
     65 		};
     66 	};
     67 	if (len(a) < len(b)) {
     68 		return -1;
     69 	} else if (len(a) == len(b)) {
     70 		return 0;
     71 	} else {
     72 		return 1;
     73 	};
     74 };
     75 
     76 // Get the dependencies for a module from the cache, recalculating
     77 // them if necessary. cachedir should be calculated with [[get_cache]],
     78 // and srcset should be calculated with [[find]].
     79 fn get_deps(cachedir: str, srcs: *srcset) ([]ast::ident | error) = {
     80 	static let buf = path::buffer{...};
     81 	path::set(&buf, cachedir, "deps")?;
     82 	let rest = memio::fixed(buf.buf[buf.end..]);
     83 	buf.end += format_tags(&rest, srcs.seentags)?;
     84 	buf.end += memio::concat(&rest, ".txt")?;
     85 
     86 	let outofdate = outdated(path::string(&buf), srcs.ha, srcs.mtime);
     87 	os::mkdirs(cachedir, 0o755)?;
     88 	let depsfile = os::create(path::string(&buf), 0o644, fs::flag::RDWR)?;
     89 	defer io::close(depsfile)!;
     90 	io::lock(depsfile, true, io::lockop::EXCLUSIVE)?;
     91 
     92 	let deps: []ast::ident = [];
     93 	if (outofdate) {
     94 		deps = parse_deps(srcs.ha...)?;
     95 		io::trunc(depsfile, 0)?;
     96 		let out = bufio::init(depsfile, [], buf.buf);
     97 		for (let dep .. deps) {
     98 			unparse::ident(&out, dep)?;
     99 			fmt::fprintln(&out)?;
    100 		};
    101 	} else {
    102 		let in = bufio::newscanner_static(depsfile, buf.buf);
    103 		for (let s => bufio::scan_line(&in)?) {
    104 			append(deps, parse::identstr(s)?);
    105 		};
    106 	};
    107 	return deps;
    108 };
    109 
    110 // Gather a [[module]] and all its dependencies, appending them to an existing
    111 // slice, deduplicated, in reverse topological order, returning the index of the
    112 // input module within the slice. Dependencies will also be written to the
    113 // cache.
    114 export fn gather(
    115 	ctx: *context,
    116 	out: *[]module,
    117 	mod: location,
    118 ) (size | error) = {
    119 	let stack: []str = [];
    120 	defer free(stack);
    121 	return _gather(ctx, out, &stack, mod)?;
    122 };
    123 
    124 fn _gather(
    125 	ctx: *context,
    126 	out: *[]module,
    127 	stack: *[]str,
    128 	mod: location,
    129 ) (size | error) = {
    130 	let (modpath, srcs) = match (find(ctx, mod)) {
    131 	case let r: (str, srcset) =>
    132 		yield r;
    133 	case let e: error =>
    134 		if (len(stack) == 0) {
    135 			return e;
    136 		};
    137 		return attach(strings::dup(stack[len(stack) - 1]), e);
    138 	};
    139 	modpath = strings::dup(modpath);
    140 	defer free(modpath);
    141 
    142 	for (let j = 0z; j < len(stack); j += 1) {
    143 		if (modpath == stack[j]) {
    144 			append(stack, modpath);
    145 			return strings::dupall(stack[j..]): dep_cycle;
    146 		};
    147 	};
    148 	for (let j = 0z; j < len(out); j += 1) {
    149 		if (modpath == out[j].path) {
    150 			return j;
    151 		};
    152 	};
    153 	append(stack, modpath);
    154 	defer delete(stack[len(stack) - 1]);
    155 
    156 	let cache = get_cache(ctx.harecache, modpath)?;
    157 	let depids = get_deps(cache, &srcs)?;
    158 	defer free(depids);
    159 	let deps: [](size, ast::ident) = alloc([], len(depids));
    160 	for (let i = 0z; i < len(depids); i += 1) {
    161 		static append(deps,
    162 			(_gather(ctx, out, stack, depids[i])?, depids[i])
    163 		);
    164 	};
    165 
    166 	append(out, module {
    167 		name = match (mod) {
    168 		case let mod: *path::buffer =>
    169 			yield strings::dup(path::string(mod));
    170 		case let mod: ast::ident =>
    171 			yield unparse::identstr(mod);
    172 		},
    173 		ns = match (mod) {
    174 		case let mod: *path::buffer =>
    175 			yield [];
    176 		case let mod: ast::ident =>
    177 			yield ast::ident_dup(mod);
    178 		},
    179 		path = strings::dup(modpath),
    180 		srcs = srcs,
    181 		deps = deps,
    182 	});
    183 	return len(out) - 1;
    184 };
    185 
    186 // Free the resources associated with a [[module]].
    187 export fn finish(mod: *module) void = {
    188 	free(mod.name);
    189 	ast::ident_free(mod.ns);
    190 	free(mod.path);
    191 	finish_srcset(&mod.srcs);
    192 	for (let (_, ident) .. mod.deps) {
    193 		ast::ident_free(ident);
    194 	};
    195 	free(mod.deps);
    196 };
    197 
    198 // Free all the [[module]]s in a slice of modules, and then the slice itself.
    199 export fn free_slice(mods: []module) void = {
    200 	for (let mod &.. mods) {
    201 		finish(mod);
    202 	};
    203 	free(mods);
    204 };