z_mirror/hare-fmm
git clone https://git.raiguard.me/z_mirror/hare-fmm
74b341b
trunk
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};