///|
/// Immutable-by-interface resolution snapshot detached from user input.
pub struct Resolution {
priv values : Map[String, Value]
priv visibility : Map[String, Tri]
priv reasons : Map[String, String]
priv messages : Array[String]
}
///|
pub fn Resolution::value(
self : Resolution,
name : String,
) -> Value raise ConfigError {
match self.values.get(name) {
Some(v) => v
None => raise Invalid("unknown resolved symbol " + name)
}
}
///|
pub fn Resolution::values(self : Resolution) -> Map[String, Value] {
let out = Map([])
for key, value in self.values {
out[key] = value
}
out
}
///|
pub fn Resolution::visibility(
self : Resolution,
name : String,
) -> Tri raise ConfigError {
match self.visibility.get(name) {
Some(v) => v
None => raise Invalid("unknown visibility " + name)
}
}
///|
pub fn Resolution::reason(
self : Resolution,
name : String,
) -> String raise ConfigError {
match self.reasons.get(name) {
Some(v) => v
None => raise Invalid("unknown reason " + name)
}
}
///|
pub fn Resolution::diagnostics(self : Resolution) -> Array[String] {
self.messages.copy()
}
///|
fn logical_bound(value : Tri, kind : Kind) -> Tri {
if kind != Tristate && value == M {
Y
} else {
value
}
}
///|
fn typed_default(value : Value, kind : Kind) -> Value raise ConfigError {
match kind {
Boolean | Tristate => Logic(value.logic())
StringType => Text(value.text())
Integer | Hex =>
match value {
Unset | Number(_) => value
_ => Number(parse_number(value.text()))
}
}
}
///|
fn direct_visibility(
schema : Schema,
d : Definition,
kind : Kind,
values : Map[String, Value],
) -> Tri {
if d.prompt == None {
N
} else {
logical_bound(
d.depends
.condition(
values,
schema.modules_enabled(values),
linux612=schema.linux612,
)
.meet(
d.prompt_if.condition(
values,
schema.modules_enabled(values),
linux612=schema.linux612,
),
),
kind,
)
}
}
///|
fn base_value(
schema : Schema,
d : Definition,
kind : Kind,
requests : Map[String, Value],
values : Map[String, Value],
) -> (Value, String) raise ConfigError {
if schema.linux612 && ["n", "m", "y"].contains(d.name) {
return (
Logic(
match d.name {
"y" => Y
"m" => M
_ => N
},
),
"constant",
)
}
if schema.linux612 && d.kind == None {
return (Text(d.name), "undefined-type")
}
let visible = direct_visibility(schema, d, kind, values)
match requests.get(d.name) {
Some(v) =>
if visible != N &&
(schema.linux612 || request_in_range(schema, d, v, values)) {
let v = if schema.linux612 {
clamp_default(schema, d, v, values)
} else {
v
}
return (
match v {
Logic(t) => Logic(logical_bound(t.meet(visible), kind))
_ => v
},
"user",
)
}
None => ()
}
for (expr, condition) in d.defaults {
let active = condition
.condition(
values,
schema.modules_enabled(values),
linux612=schema.linux612,
)
.meet(
d.depends.condition(
values,
schema.modules_enabled(values),
linux612=schema.linux612,
),
)
if active != N {
let raw = expr.typed_value(kind, values, linux612=schema.linux612)
let value = clamp_default(schema, d, raw, values)
return (
match value {
Logic(t) => Logic(logical_bound(t.meet(active), kind))
_ => value
},
"default",
)
}
}
(
clamp_default(
schema,
d,
if schema.linux612 {
linux_initial(kind)
} else {
kind.initial()
},
values,
),
"initial",
)
}
///|
/// Resolve to a bounded fixed point; requests never override hidden prompts.
pub fn Schema::resolve(
self : Schema,
fragment : Fragment,
) -> Resolution raise ConfigError {
ignore(self.dependency_order())
let requests = fragment.values()
for name, value in requests {
if !self.kind(name).accepts(value) {
raise Invalid("request/schema type mismatch: " + name)
}
}
let mut values : Map[String, Value] = Map([])
for d in self.defs {
values[d.name] = if self.linux612 && d.kind == None {
Text(d.name)
} else if self.linux612 {
linux_initial(self.kind(d.name))
} else {
self.kind(d.name).initial()
}
}
let reasons : Map[String, String] = Map([])
for _ in 0..<(self.defs.length() * 4 + 32) {
let next : Map[String, Value] = Map([])
for d in self.defs {
let kind = self.effective_kind(d.name, values)
let (base, why) = base_value(self, d, kind, requests, values)
let (value, reason) = apply_reverse(
self, d, kind, base, why, requests, values,
)
next[d.name] = value
reasons[d.name] = reason
}
apply_choices(self, fragment, values, next, reasons)
let changed = self.defs.any(fn(d) { values.get(d.name) != next.get(d.name) })
values = next
if !changed {
let visibility : Map[String, Tri] = Map([])
let messages = []
for d in self.defs {
let strong = reverse_strength(self, d.name, values, false)
if strong.rank() >
d.depends
.condition(
values,
self.modules_enabled(values),
linux612=self.linux612,
)
.rank() {
messages.push("select bypasses dependencies: " + d.name)
}
visibility[d.name] = direct_visibility(
self,
d,
self.effective_kind(d.name, values),
values,
)
match requests.get(d.name) {
Some(wanted) =>
if values.get(d.name) != Some(wanted) {
messages.push("request constrained: " + d.name)
}
None => ()
}
}
for c in self.choices {
let mode = !c.optional ||
fragment.entries.any(fn(e) {
c.members.contains(e.0) && e.1 == Logic(Y)
})
if !mode {
for name in c.members {
visibility[name] = N
}
}
}
return { values, visibility, reasons, messages, }
}
}
raise Invalid(
"configuration did not converge; inspect recursive dependencies",
)
}