///|
/// Validated user requests; the resolver may constrain them by visibility.
pub struct Fragment {
priv entries : Array[(String, Value)]
}
///|
pub fn Fragment::new() -> Fragment {
{ entries: [], }
}
///|
pub fn Fragment::values(self : Fragment) -> Map[String, Value] {
let out = Map([])
for (name, value) in self.entries {
out[name] = value
}
out
}
///|
pub fn Schema::request(
self : Schema,
requests : Fragment,
name : String,
value : Value,
) -> Unit raise ConfigError {
if value is Text(s) {
ignore(quote_string(s))
}
let kind = self.kind(name)
if !kind.accepts(value) {
raise Invalid("invalid request type for " + name)
}
if kind == Hex && value is Number(n) && n < 0L {
raise Invalid("hex request must be nonnegative")
}
for i, entry in requests.entries {
if entry.0 == name {
for j in i..<(requests.entries.length() - 1) {
requests.entries[j] = requests.entries[j + 1]
}
ignore(requests.entries.pop())
break
}
}
requests.entries.push((name, value))
}
///|
/// Rightmost fragment wins. Returned fragments never alias input arrays.
pub fn merge_fragments(fragments : Array[Fragment]) -> Fragment {
let out = Fragment::new()
for fragment in fragments {
for entry in fragment.entries {
for i, previous in out.entries {
if previous.0 == entry.0 {
for j in i..<(out.entries.length() - 1) {
out.entries[j] = out.entries[j + 1]
}
ignore(out.entries.pop())
break
}
}
out.entries.push(entry)
}
}
out
}
///|
fn config_value(kind : Kind, raw : String) -> Value raise ConfigError {
match kind {
Boolean | Tristate => {
let v = Logic(parse_tri(raw))
if !kind.accepts(v) {
raise Invalid("m is not valid for a boolean")
}
v
}
StringType => {
let ts = tokenize(raw)
if ts.length() != 1 || !ts[0].quoted {
raise Invalid("string assignment must be quoted")
}
Text(ts[0].text)
}
Integer | Hex => {
if kind == Integer && (raw.contains("x") || raw.contains("X")) {
raise Invalid("integer assignments require decimal digits")
}
let value = if kind == Hex { parse_hex(raw) } else { parse_number(raw) }
if kind == Hex && value < 0L {
raise Invalid("hex assignment must be nonnegative")
}
Number(value)
}
}
}
///|
/// Strict .config reader: unknown symbols and duplicates are actionable errors.
pub fn Schema::parse_config(
self : Schema,
text : String,
) -> Fragment raise ConfigError {
if text.length() > 4194304 {
raise Invalid("config input exceeds size limit")
}
let out = Fragment::new()
let seen : Map[String, Bool] = Map([])
for line_no, physical in text.split("\n").to_array() {
let line = physical.trim().to_owned()
if line.is_empty() {
continue
}
try {
let (name, value) = if line.has_prefix("# CONFIG_") &&
line.has_suffix(" is not set") {
let name = line[9:line.length() - 11].to_owned()
let kind = self.kind(name)
if kind != Boolean && kind != Tristate {
raise Invalid("unset syntax needs logical symbol")
}
(name, Logic(N))
} else if line.has_prefix("#") {
continue
} else {
let pieces = line.split("=").to_array()
if pieces.length() < 2 || !pieces[0].has_prefix("CONFIG_") {
raise Invalid("expected CONFIG_NAME=value")
}
let name = pieces[0][7:].to_owned()
let start = pieces[0].length() + 1
(
name,
if self.linux612 &&
(self.kind(name) == Integer || self.kind(name) == Hex) {
let raw = line[start:].to_owned()
let kind = self.kind(name)
if !valid_linux_number(raw, kind) {
raise Invalid("invalid Linux numeric assignment")
}
RawNumber(raw, kind)
} else {
config_value(self.kind(name), line[start:].to_owned())
},
)
}
if seen.contains(name) {
raise Invalid("duplicate assignment " + name)
}
seen[name] = true
self.request(out, name, value)
} catch {
Invalid(msg) =>
raise Invalid(".config:" + (line_no + 1).to_string() + ": " + msg)
}
}
out
}