///|
/// Detect an optional UNA and the UNB syntax declaration. Only syntax 3 is supported.
pub fn detect_syntax(input : Bytes) -> Result[SyntaxProfile, Diagnostic] {
let explicit_una = input.length() >= 3 &&
input[0] == b'U' &&
input[1] == b'N' &&
input[2] == b'A'
if explicit_una && input.length() < 9 {
return Err(
diagnostic(
"S_UNA_TRUNCATED",
Syntax,
0,
input.length(),
"UNA requires exactly six service characters.",
),
)
}
let component = if explicit_una { input[3] } else { b':' }
let element = if explicit_una { input[4] } else { b'+' }
let decimal = if explicit_una { input[5] } else { b'.' }
let release = if explicit_una { input[6] } else { b'?' }
let reserved = if explicit_una { input[7] } else { b' ' }
let terminator = if explicit_una { input[8] } else { b'\'' }
let services = [component, element, decimal, release, terminator]
let positions = [3, 4, 5, 6, 8]
for i = 0; i < services.length(); i = i + 1 {
let b = services[i]
if b < b'!' ||
b > b'~' ||
(b >= b'0' && b <= b'9') ||
(b >= b'A' && b <= b'Z') ||
(b >= b'a' && b <= b'z') {
return Err(
diagnostic(
"S_SERVICE_INVALID",
Syntax,
positions[i],
positions[i] + 1,
"Service characters must be distinct printable ASCII punctuation.",
),
)
}
for j = 0; j < i; j = j + 1 {
if services[j] == b {
return Err(
diagnostic(
"S_SERVICE_DUPLICATE",
Syntax,
positions[i],
positions[i] + 1,
"Service characters must be distinct.",
),
)
}
}
}
if decimal != b'.' && decimal != b',' {
return Err(
diagnostic(
"S_DECIMAL_INVALID",
Syntax,
5,
6,
"The decimal mark must be dot or comma.",
),
)
}
let mut body_start = if explicit_una { 9 } else { 0 }
while body_start < input.length() &&
(input[body_start] == b'\r' || input[body_start] == b'\n') {
body_start = body_start + 1
}
if input.length() - body_start >= 3 &&
input[body_start] == b'U' &&
input[body_start + 1] == b'I' &&
(
input[body_start + 2] == b'B' ||
input[body_start + 2] == b'H' ||
input[body_start + 2] == b'T' ||
input[body_start + 2] == b'Z'
) {
return Err(
diagnostic(
"S_INTERACTIVE_UNSUPPORTED",
Syntax,
body_start,
body_start + 3,
"Interactive EDIFACT syntax is not supported in 0.1.0.",
),
)
}
if input.length() - body_start < 4 ||
input[body_start] != b'U' ||
input[body_start + 1] != b'N' ||
input[body_start + 2] != b'B' ||
input[body_start + 3] != element {
return Err(
diagnostic(
"S_UNB_MISSING",
Syntax,
body_start,
body_start + (input.length() - body_start).min(4),
"Expected UNB as the first segment.",
),
)
}
let start = body_start + 4
if input.length() - start < 7 ||
input[start + 4] != component ||
(input[start + 6] != element && input[start + 6] != terminator) {
return Err(
diagnostic(
"S_SYNTAX_DECLARATION",
Syntax,
start,
start + (input.length() - start).min(7),
"Expected a four-letter syntax identifier and one syntax version digit.",
),
)
}
let name = ascii_text(input, start, start + 4)
if name != "UNOA" && name != "UNOB" {
return Err(
diagnostic(
"S_CHARSET_UNSUPPORTED",
Syntax,
start,
start + 4,
"Only UNOA and the printable ASCII subset of UNOB are supported.",
),
)
}
if input[start + 5] != b'3' {
return Err(
diagnostic(
"S_VERSION_UNSUPPORTED",
Syntax,
start + 5,
start + 6,
"Only syntax version 3 is supported.",
),
)
}
if reserved != b' ' {
return Err(
diagnostic(
"S_UNA_RESERVED",
Syntax,
7,
8,
"Syntax 3 requires a space at UNA position 5.",
),
)
}
Ok({
component,
element,
decimal,
release,
reserved,
terminator,
charset: name,
version: 3,
explicit_una,
body_start,
})
}
///|
fn ascii_text(input : Bytes, start : Int, end : Int) -> String {
let text = StringBuilder()
for i = start; i < end; i = i + 1 {
text.write_char(input[i].to_char())
}
text.to_string()
}