z_mirror/hare-fmm

git clone https://git.raiguard.me/z_mirror/hare-fmm

Caleb HeuerConvert to for-each loops and fix some potential memory issues74b341b

trunk
4.0 KiB145 linesraw
1use fmt;
2use regex;
3use strings;
4
5type dependency = struct {
6	ident: modident,
7	kind: dependency_kind,
8	req: version_cmpres,
9};
10
11type dependency_error = !(regex::error | version_error | nomem);
12
13type dependency_kind = enum {
14	HIDDEN_OPTIONAL,
15	INCOMPATIBLE,
16	NO_LOAD_ORDER,
17	OPTIONAL,
18	REQUIRED,
19};
20
21// Frees resources related to a [[dependency]].
22fn dependency_free(dep: *dependency) void = {
23	modident_free(&dep.ident);
24};
25
26// Frees a slice of [[dependency]], then the slice itself.
27fn dependency_free_all(deps: []dependency) void = {
28	for (let dep &.. deps)
29		dependency_free(dep);
30	free(deps);
31};
32
33// Parses a [[dependency]] from an input string. The return value must be freed
34// with [[dependency_free]].
35fn dependency_parse(input: str) (dependency | dependency_error) = {
36	const input = strings::trim(input);
37
38	let kind = dependency_kind::REQUIRED;
39	let name_i = 0z;
40	if (strings::hasprefix(input, '!')) {
41		kind = dependency_kind::INCOMPATIBLE;
42		name_i = 1;
43	} else if (strings::hasprefix(input, '?')) {
44		kind = dependency_kind::OPTIONAL;
45		name_i = 1;
46	} else if (strings::hasprefix(input, "(?)")) {
47		kind = dependency_kind::HIDDEN_OPTIONAL;
48		name_i = 3;
49	} else if (strings::hasprefix(input, "~")) {
50		kind = dependency_kind::NO_LOAD_ORDER;
51		name_i = 1;
52	};
53
54	let req = version_cmpres::ANY;
55	let req_i = len(input);
56	if (strings::contains(input, "<=")) {
57		req = version_cmpres::LTEQ;
58		req_i = strings::byteindex(input, "<=") as size;
59	} else if (strings::contains(input, '<')) {
60		req = version_cmpres::LT;
61		req_i = strings::byteindex(input, '<') as size;
62	} else if (strings::contains(input, ">=")) {
63		req = version_cmpres::GTEQ;
64		req_i = strings::byteindex(input, ">=") as size;
65	} else if (strings::contains(input, '>')) {
66		req = version_cmpres::GT;
67		req_i = strings::byteindex(input, '>') as size;
68	} else if (strings::contains(input, '=')) {
69		req = version_cmpres::EQ;
70		req_i = strings::byteindex(input, '=') as size;
71	};
72
73	// TODO: Using a regex here is overkill.
74	let ver: (version | void) = void;
75	if (req != version_cmpres::ANY) {
76		// Find the starting index of the version
77		let re = regex::compile(`[^\.][:digit:]*\.[:digit:]*`)?;
78		let result = regex::find(&re, input)!;
79		defer regex::result_free(result);
80		const verstr = strings::sub(input, result[0].start, strings::end);
81		ver = version_parse(verstr)?;
82	};
83
84	// Mod name
85	let name = strings::dup(strings::trim(strings::sub(input, name_i, req_i)))!;
86
87	return dependency {
88		ident = (name, ver),
89		kind = kind,
90		req = req,
91	};
92};
93
94// Converts a [[dependency]] into a user-friendly string.
95fn dependency_str(dep: *dependency) str = {
96	const kind = switch (dep.kind) {
97	case dependency_kind::HIDDEN_OPTIONAL => yield "(?) ";
98	case dependency_kind::INCOMPATIBLE => yield "! ";
99	case dependency_kind::NO_LOAD_ORDER => yield "~ ";
100	case dependency_kind::OPTIONAL => yield "? ";
101	case dependency_kind::REQUIRED => yield "";
102	};
103
104	const req = if (dep.req != version_cmpres::ANY) {
105		const req = switch (dep.req) {
106		case version_cmpres::ANY => yield "";
107		case version_cmpres::EQ => yield " =";
108		case version_cmpres::GT => yield " >";
109		case version_cmpres::GTEQ => yield " >=";
110		case version_cmpres::LT => yield " <";
111		case version_cmpres::LTEQ => yield " <=";
112		};
113		const ver = version_str(dep.ident.1 as version, false);
114		defer free(ver);
115		yield fmt::asprint(req, ver)!;
116	} else {
117		yield strings::dup("")!;
118	};
119	defer free(req);
120
121	return fmt::asprintf("{}{}{}", kind, dep.ident.0, req)!;
122};
123
124// Tests if the given [[modident]] satisfies the given [[dependency]]. Note that
125// incompatible-kind dependencies will always return false.
126fn dependency_test(dep: *dependency, mod: *modident) bool = {
127	// Name
128	if (dep.ident.0 != mod.0)
129		return false;
130	// Incompatible
131	if (dep.kind == dependency_kind::INCOMPATIBLE)
132		return false;
133
134	const dep_ver = dep.ident.1;
135	const req = dep.req;
136	const mod_ver = mod.1 as version;
137
138	if (req == version_cmpres::ANY)
139		return true;
140	match (dep_ver) {
141	case void => return false;
142	case let dep_ver: version =>
143		return (req & version_cmp(mod_ver, dep_ver) > 0);
144	};
145};