///|
priv struct SourceLine {
text : String
location : String
}
///|
fn logical_lines(
text : String,
file : String,
) -> Array[SourceLine] raise ConfigError {
if text.length() > 4194304 {
raise Invalid("source too large")
}
let out : Array[SourceLine] = []
let mut pending = ""
let mut start = 1
let mut n = 0
for raw in text.split("\n") {
n += 1
let line = raw.trim_end(chars="\r").to_owned()
if pending.is_empty() {
start = n
}
let continued = line.has_suffix("\\")
pending += if continued {
line[0:line.length() - 1].to_owned()
} else {
line
}
if pending.length() > 65536 {
raise Invalid("logical source line too long")
}
if !continued {
out.push({ text: pending, location: file + ":\{start}", })
pending = ""
}
}
if !pending.is_empty() {
raise Invalid("dangling source continuation")
}
out
}
///|
fn safe_source_path(path : String) -> String raise ConfigError {
if path.is_empty() ||
path.has_prefix("/") ||
path.contains("\\") ||
path.contains(":") {
raise Invalid("source path must be relative")
}
let parts : Array[String] = []
for part in path.split("/") {
if part == ".." {
if parts.is_empty() {
raise Invalid("source path escapes source tree")
}
ignore(parts.pop())
} else if part != "." && !part.is_empty() {
parts.push(part.to_owned())
}
}
if parts.is_empty() {
raise Invalid("empty source path")
}
parts.join("/")
}
///|
fn indentation(text : String) -> Int32 {
let mut n = 0
while n < text.length() && (text[n] == 32 || text[n] == 9) {
n += 1
}
n
}
///|
fn expand_sources(
file : String,
sources : Map[String, String],
active : Array[String],
output : Array[SourceLine],
budget : Ref[Int32],
preprocessor : Preprocessor,
linux612 : Bool,
) -> Unit raise ConfigError {
if active.length() >= 32 || active.contains(file) {
raise Invalid("cyclic or overdeep source: " + file)
}
let text = match sources.get(file) {
Some(t) => t
None => raise Invalid("missing source: " + file)
}
active.push(file)
let mut help_indent : Int32? = None
let mut help_margin : Int32? = None
for original in logical_lines(text, file) {
let line = if linux612 {
{ text: expand_indent_tabs(original.text), location: original.location, }
} else {
original
}
budget.val += line.text.length() + 1
if budget.val > 16777216 || output.length() >= 65536 {
raise Invalid("expanded source resource budget exceeded")
}
match help_indent {
Some(level) => {
if line.text.trim().is_empty() ||
(if linux612 {
indentation(line.text) >= help_margin.unwrap_or(1)
} else {
indentation(line.text) > level
}) {
if !line.text.trim().is_empty() && help_margin == None {
help_margin = Some(indentation(line.text))
}
output.push(line)
continue
}
help_indent = None
}
None => ()
}
let processed = preprocessor.line(line) catch {
Invalid(msg) => raise Invalid(line.location + ": " + msg)
}
let expanded = match processed {
Some(text) => text
None => continue
}
let line = { text: expanded, location: line.location, }
let ts = tokenize(line.text) catch {
Invalid(msg) => raise Invalid(line.location + ": " + msg)
}
if !ts.is_empty() && (ts[0].text == "help" || ts[0].text == "---help---") {
help_indent = Some(indentation(line.text))
help_margin = None
}
if !ts.is_empty() &&
(
ts[0].text == "source" ||
ts[0].text == "rsource" ||
ts[0].text == "osource" ||
ts[0].text == "orsource"
) {
if linux612 && ts[0].text != "source" {
raise Invalid("source extension outside Linux v6.12")
}
if ts.length() != 2 || !ts[1].quoted {
raise Invalid(line.location + ": source needs quoted path")
}
let mut path = ts[1].text
if ts[0].text == "rsource" || ts[0].text == "orsource" {
match file.rev_split_once("/") {
Some((dir, _)) => path = dir.to_owned() + "/" + path
None => ()
}
}
path = safe_source_path(path)
let matches = []
for candidate, _ in sources {
if (if linux612 {
path == candidate
} else {
source_pattern(path, candidate)
}) {
matches.push(candidate)
}
}
matches.sort()
if matches.is_empty() &&
(ts[0].text == "source" || ts[0].text == "rsource") {
raise Invalid("missing source: " + path)
}
for found in matches {
expand_sources(
found, sources, active, output, budget, preprocessor, linux612,
)
}
} else {
output.push(line)
}
if output.length() > 65536 {
raise Invalid("expanded source exceeds 65536 lines")
}
}
ignore(active.pop())
}
///|
/// Resolve explicit in-memory source files; never reads disk, shell or environment.
pub fn parse_sources(
entry : String,
sources : Map[String, String],
preprocessor~ : Preprocessor = Preprocessor::new(),
linux612~ : Bool = false,
) -> Schema raise ConfigError {
let out = []
expand_sources(
safe_source_path(entry),
sources,
[],
out,
Ref(0),
preprocessor,
linux612,
)
parse_lines(out, linux612~)
}
///|
/// Linux v6.12 language profile. Files and host preprocessor callbacks remain
/// explicit; the library does not execute commands or read ambient files.
pub fn parse_linux612(
entry : String,
sources : Map[String, String],
preprocessor~ : Preprocessor = Preprocessor::new(),
) -> Schema raise ConfigError {
parse_sources(entry, sources, preprocessor~, linux612=true)
}
///|
fn expand_indent_tabs(text : String) -> String {
let mut i = 0
let mut column = 0
while i < text.length() && (text[i] == 32 || text[i] == 9) {
column = if text[i] == 9 { (column / 8 + 1) * 8 } else { column + 1 }
i += 1
}
let spaces : Array[String] = []
for _ in 0..