use fmt; use regex; use strings; type dependency = struct { ident: modident, kind: dependency_kind, req: version_cmpres, }; type dependency_error = !(regex::error | version_error | nomem); type dependency_kind = enum { HIDDEN_OPTIONAL, INCOMPATIBLE, NO_LOAD_ORDER, OPTIONAL, REQUIRED, }; // Frees resources related to a [[dependency]]. fn dependency_free(dep: *dependency) void = { modident_free(&dep.ident); }; // Frees a slice of [[dependency]], then the slice itself. fn dependency_free_all(deps: []dependency) void = { for (let dep &.. deps) dependency_free(dep); free(deps); }; // Parses a [[dependency]] from an input string. The return value must be freed // with [[dependency_free]]. fn dependency_parse(input: str) (dependency | dependency_error) = { const input = strings::trim(input); let kind = dependency_kind::REQUIRED; let name_i = 0z; if (strings::hasprefix(input, '!')) { kind = dependency_kind::INCOMPATIBLE; name_i = 1; } else if (strings::hasprefix(input, '?')) { kind = dependency_kind::OPTIONAL; name_i = 1; } else if (strings::hasprefix(input, "(?)")) { kind = dependency_kind::HIDDEN_OPTIONAL; name_i = 3; } else if (strings::hasprefix(input, "~")) { kind = dependency_kind::NO_LOAD_ORDER; name_i = 1; }; let req = version_cmpres::ANY; let req_i = len(input); if (strings::contains(input, "<=")) { req = version_cmpres::LTEQ; req_i = strings::byteindex(input, "<=") as size; } else if (strings::contains(input, '<')) { req = version_cmpres::LT; req_i = strings::byteindex(input, '<') as size; } else if (strings::contains(input, ">=")) { req = version_cmpres::GTEQ; req_i = strings::byteindex(input, ">=") as size; } else if (strings::contains(input, '>')) { req = version_cmpres::GT; req_i = strings::byteindex(input, '>') as size; } else if (strings::contains(input, '=')) { req = version_cmpres::EQ; req_i = strings::byteindex(input, '=') as size; }; // TODO: Using a regex here is overkill. let ver: (version | void) = void; if (req != version_cmpres::ANY) { // Find the starting index of the version let re = regex::compile(`[^\.][:digit:]*\.[:digit:]*`)?; let result = regex::find(&re, input)!; defer regex::result_free(result); const verstr = strings::sub(input, result[0].start, strings::end); ver = version_parse(verstr)?; }; // Mod name let name = strings::dup(strings::trim(strings::sub(input, name_i, req_i)))!; return dependency { ident = (name, ver), kind = kind, req = req, }; }; // Converts a [[dependency]] into a user-friendly string. fn dependency_str(dep: *dependency) str = { const kind = switch (dep.kind) { case dependency_kind::HIDDEN_OPTIONAL => yield "(?) "; case dependency_kind::INCOMPATIBLE => yield "! "; case dependency_kind::NO_LOAD_ORDER => yield "~ "; case dependency_kind::OPTIONAL => yield "? "; case dependency_kind::REQUIRED => yield ""; }; const req = if (dep.req != version_cmpres::ANY) { const req = switch (dep.req) { case version_cmpres::ANY => yield ""; case version_cmpres::EQ => yield " ="; case version_cmpres::GT => yield " >"; case version_cmpres::GTEQ => yield " >="; case version_cmpres::LT => yield " <"; case version_cmpres::LTEQ => yield " <="; }; const ver = version_str(dep.ident.1 as version, false); defer free(ver); yield fmt::asprint(req, ver)!; } else { yield strings::dup("")!; }; defer free(req); return fmt::asprintf("{}{}{}", kind, dep.ident.0, req)!; }; // Tests if the given [[modident]] satisfies the given [[dependency]]. Note that // incompatible-kind dependencies will always return false. fn dependency_test(dep: *dependency, mod: *modident) bool = { // Name if (dep.ident.0 != mod.0) return false; // Incompatible if (dep.kind == dependency_kind::INCOMPATIBLE) return false; const dep_ver = dep.ident.1; const req = dep.req; const mod_ver = mod.1 as version; if (req == version_cmpres::ANY) return true; match (dep_ver) { case void => return false; case let dep_ver: version => return (req & version_cmp(mod_ver, dep_ver) > 0); }; };