///|
/// Encode bytes as lowercase hexadecimal for portable binary Ion fixtures.
pub fn encode_hex(input : Bytes) -> String {
let digits = "0123456789abcdef"
let builder = StringBuilder::new(size_hint=input.length() * 2)
for byte in input {
let value = byte.to_int()
builder.write_char(digits.get_char(value >> 4).unwrap())
builder.write_char(digits.get_char(value & 0x0f).unwrap())
}
builder.to_string()
}
///|
/// Decode hexadecimal fixture text.
///
/// ASCII whitespace and `#` line comments are ignored; every remaining
/// character must be a hexadecimal digit and digits must occur in pairs.
pub fn decode_hex(input : String) -> Bytes raise @model.IonError {
let result : Array[Byte] = []
let mut high : Int? = None
let mut offset = 0
let mut line = 1
let mut column = 1
let mut in_comment = false
for character in input {
if in_comment {
if character == '\n' {
in_comment = false
}
} else if character == '#' {
in_comment = true
} else if character.is_ascii_whitespace() {
()
} else {
match hex_digit(character) {
Some(digit) =>
match high {
Some(first) => {
result.push(((first << 4) | digit).to_byte())
high = None
}
None => high = Some(digit)
}
None =>
raise hex_error(
@model.ErrorKind::InvalidSyntax,
offset,
line,
column,
"hex fixtures may contain only hexadecimal digits, whitespace, and # comments",
)
}
}
offset += 1
if character == '\n' {
line += 1
column = 1
} else {
column += 1
}
}
match high {
Some(_) =>
raise hex_error(
@model.ErrorKind::InvalidLength,
offset,
line,
column,
"hex fixtures must contain complete byte pairs",
)
None => Bytes::from_array(result)
}
}
///|
fn hex_digit(character : Char) -> Int? {
let value = character.to_int()
if value >= '0'.to_int() && value <= '9'.to_int() {
Some(value - '0'.to_int())
} else if value >= 'a'.to_int() && value <= 'f'.to_int() {
Some(value - 'a'.to_int() + 10)
} else if value >= 'A'.to_int() && value <= 'F'.to_int() {
Some(value - 'A'.to_int() + 10)
} else {
None
}
}
///|
fn hex_error(
kind : @model.ErrorKind,
offset : Int,
line : Int,
column : Int,
message : String,
) -> @model.IonError {
@model.IonError::Parse(kind~, offset~, line~, column~, message~)
}
///|
/// Result of comparing text and binary Ion documents without normalizing them.
pub(all) struct InteropReport {
text_values : Int
binary_values : Int
matches : Bool
} derive(Debug, Eq)
///|
/// Return the number of top-level values found in the text input.
pub fn InteropReport::text_value_count(self : InteropReport) -> Int {
self.text_values
}
///|
/// Return the number of top-level values found in the binary input.
pub fn InteropReport::binary_value_count(self : InteropReport) -> Int {
self.binary_values
}
///|
/// Report whether parsed values are exactly equal, including annotations,
/// symbols, duplicate fields, and field order.
pub fn InteropReport::matches(self : InteropReport) -> Bool {
self.matches
}
///|
/// Parse text and binary Ion and compare their preserved value models.
pub fn diagnose_text_binary(
text : String,
binary : Bytes,
limits? : @model.Limits,
) -> InteropReport raise @model.IonError {
let limits = limits.unwrap_or(@model.Limits::default())
let text_values = parse_text(text, limits~)
let binary_values = parse_binary(binary, limits~)
{
text_values: text_values.length(),
binary_values: binary_values.length(),
matches: text_values == binary_values,
}
}