///|
fn byte_label(value : Int32) -> String raise {
if value == 32 {
"32 SPACE"
} else if value >= 33 && value <= 126 {
let text = @utf8.decode(Bytes::from_array([value.to_byte()]))
"\{value} '\{text}'"
} else {
value.to_string()
}
}
///|
fn show_case(
label : String,
program : @abnf.Program,
rule : String,
input : Bytes,
revised : Bytes,
field : String,
) -> Unit raise {
let result = program.diagnose_input(rule, input)
assert_eq(result.report.status, @abnf.NoMatch)
let failure = result.failure.unwrap()
let left = if failure.offset > 6 { failure.offset - 6 } else { 0 }
let right = if failure.offset + 6 < input.length() {
failure.offset + 6
} else {
input.length()
}
println("\{label}: input=\{input}")
println(
" byte \{failure.offset}; nearby [\{left},\{right})=\{input[left:right]}",
)
let actual = match failure.actual {
Some(value) => byte_label(value)
None => "EOF"
}
println(" actual=\{actual}; explanation=\{result.explanation_status}")
if failure.truncated {
println(" partial expectations only; missing entries are not ruled out")
}
for item in failure.expected {
let expected = match item.expectation {
@abnf.EndOfInput => "EOF"
@abnf.ByteRange(lo, hi, insensitive) => {
let bounds = if lo == hi {
byte_label(lo)
} else {
"\{byte_label(lo)} .. \{byte_label(hi)}"
}
if insensitive {
bounds + " (ASCII case-insensitive)"
} else {
bounds
}
}
}
let position = match item.grammar_offset {
Some(offset) => "terminal expression at grammar byte \{offset}"
None => "implicit core/EOF; no user grammar position"
}
println(" expected \{expected}; \{position}")
}
println(" manual revision=\{revised} (one choice, not automatic repair)")
let parsed = program.parse_input(rule, revised)
assert_eq(parsed.status, @abnf.Matched)
let tree = parsed.tree.unwrap()
let nodes = tree.rule_nodes_in(field, tree.root).unwrap()
assert_true(!nodes.is_empty())
for node in nodes {
let (start, end) = tree.span(node).unwrap()
println(
" verified \{field} [\{start},\{end})=\{tree.extract(revised, node).unwrap()}",
)
}
}
///|
fn main raise {
println(
"Local farthest-branch explanations; syntax only, no unique repair claim.",
)
let uri = @abnf.compile(@protocols.uri_grammar()).unwrap()
let cookie = @abnf.compile(@protocols.cookie_grammar()).unwrap()
show_case(
"URI percent escape", uri, "URI", b"https://example.test/%GG", b"https://example.test/%20",
"path-abempty",
)
show_case(
"Cookie value", cookie, "cookie-string", b"session=bad value", b"session=bad-value",
"cookie-value",
)
show_case(
"Truncated URI", uri, "URI", b"https://[2001:db8::1", b"https://[2001:db8::1]",
"host",
)
show_case(
"Trailing URI space", uri, "URI", b"https://example.test/a ", b"https://example.test/a",
"path-abempty",
)
let limited = uri.diagnose_input("URI", b"https://example.test/%GG", limits={
..@abnf.default_match_limits(),
max_work: 1,
})
assert_true(limited.report.status is @abnf.BudgetExceeded(_))
assert_true(limited.failure is None)
println(
"Limited recognition: indeterminate; no rejection or repair suggestion.",
)
}