///|
fn format_number(
data : BytesView,
start : Int32,
) -> (Int32, Int32) raise HexdumpError {
let mut end = start
while end < data.length() && data[end] >= b'0' && data[end] <= b'9' {
end += 1
}
let number = @string.parse_int(format_text(data[start:end])) catch {
_ => {
bad_format(data)
0
}
}
(number, end)
}
///|
fn format_escapes(data : BytesView) -> Bytes {
let output = @buffer.Buffer()
let mut index = 0
while index < data.length() {
let mut byte = data[index]
if byte == b'\\' && index + 1 < data.length() {
index += 1
byte = match data[index] {
b'a' => b'\x07'
b'b' => b'\b'
b'f' => b'\x0c'
b'n' => b'\n'
b'r' => b'\r'
b't' => b'\t'
b'v' => b'\x0b'
byte => byte
}
}
output.write_byte(byte)
index += 1
}
output.to_bytes()
}
///|
fn parse_atoms(
body : Bytes,
byte_count : Int32?,
color : Bool,
) -> Array[PrintAtom] raise HexdumpError {
let atoms : Array[PrintAtom] = []
let mut start = 0
let mut index = 0
let mut conversions = 0
while index < body.length() {
if body[index] != b'%' {
index += 1
continue
}
let prefix = body[start:index].to_owned()
index += 1
let mut minus = false
let mut plus = false
let mut blank = false
let mut zero = false
let mut alternate = false
while index < body.length() {
match body[index] {
b'-' => minus = true
b'+' => plus = true
b' ' => blank = true
b'0' => zero = true
b'#' => alternate = true
_ => break
}
index += 1
}
let mut width = 0
if index < body.length() && body[index] >= b'0' && body[index] <= b'9' {
let (value, end) = format_number(body[:], index)
width = value
index = end
}
let mut precision : Int32? = None
if index < body.length() && body[index] == b'.' {
index += 1
if index < body.length() && body[index] >= b'0' && body[index] <= b'9' {
let (value, end) = format_number(body[:], index)
precision = Some(value)
index = end
}
}
if index == body.length() {
bad_format(body[:])
}
let byte = body[index]
index += 1
let mut name = byte.to_int().unsafe_to_char().to_string()
let conversion = match byte {
b'd' | b'i' | b'o' | b'u' | b'x' | b'X' =>
Integer(byte.to_int().unsafe_to_char())
b'e' | b'E' | b'f' | b'g' | b'G' =>
Floating(byte.to_int().unsafe_to_char())
b'c' => Character
b's' => StringValue
b'_' => {
if index == body.length() {
bad_conversion("_")
}
let kind = body[index]
index += 1
name += kind.to_int().unsafe_to_char().to_string()
match kind {
b'c' => EscapedCharacter
b'p' => PrintableCharacter
b'u' => AsciiName
b'a' | b'A' => {
if index == body.length() {
bad_conversion(name)
}
let radix = body[index]
index += 1
name += radix.to_int().unsafe_to_char().to_string()
if !(radix is (b'd' | b'o' | b'x')) {
bad_conversion(name)
}
Address(radix.to_int().unsafe_to_char(), kind == b'A')
}
_ => {
bad_conversion(name)
Character
}
}
}
_ => {
bad_conversion(name)
Character
}
}
let count = match conversion {
Address(_, _) => 0
Integer(_) => {
let count = byte_count.unwrap_or(4)
if ![1, 2, 4, 8].contains(count) {
bad_count(name)
}
count
}
Floating(_) => {
let count = byte_count.unwrap_or(8)
if count != 4 && count != 8 {
bad_count(name)
}
count
}
StringValue => {
let length = match byte_count {
Some(count) => Some(count)
None => precision
}
match length {
Some(count) => count
None => raise HexdumpError("%s requires a precision or a byte count")
}
}
_ => {
if byte_count is Some(count) && count != 1 {
bad_count(name)
}
1
}
}
if count > 0 {
conversions += 1
if byte_count is Some(_) && conversions > 1 {
raise HexdumpError("byte count with multiple conversion characters")
}
}
let mut colors : Array[ColorRule] = []
if index + 1 < body.length() &&
body[index] == b'_' &&
body[index + 1] == b'L' {
let mut closing : Int32? = None
for at in (index + 2).. CustomFormat raise HexdumpError {
let units : Array[FormatUnit] = []
let mut index = 0
let mut consumption = 0
while index < source.length() {
while index < source.length() && format_space(source[index]) {
index += 1
}
if index == source.length() {
break
}
let mut reps = 1
let mut explicit_reps = false
if source[index] >= b'0' && source[index] <= b'9' {
let (value, end) = format_number(source, index)
if end == source.length() ||
(!format_space(source[end]) && source[end] != b'/') {
bad_format(source)
}
reps = value
explicit_reps = true
index = end + 1
while index < source.length() && format_space(source[index]) {
index += 1
}
}
if index < source.length() && source[index] == b'/' {
index += 1
}
while index < source.length() && format_space(source[index]) {
index += 1
}
let mut byte_count : Int32? = None
if index < source.length() && source[index] >= b'0' && source[index] <= b'9' {
let (value, end) = format_number(source, index)
if end == source.length() || !format_space(source[end]) {
bad_format(source)
}
byte_count = if value == 0 { None } else { Some(value) }
index = end + 1
while index < source.length() && format_space(source[index]) {
index += 1
}
}
if index == source.length() || source[index] != b'"' {
bad_format(source)
}
index += 1
let start = index
while index < source.length() && source[index] != b'"' {
index += 1
}
if index == source.length() {
bad_format(source)
}
let body = format_escapes(source[start:index])
index += 1
let atoms = parse_atoms(body, byte_count, color)
let mut count = 0
let mut final_address = false
for atom in atoms {
if atom.count > 2147483647 - count {
bad_format(source)
}
count += atom.count
if atom.conversion is Some(Address(_, true)) {
final_address = true
}
}
let count = byte_count.unwrap_or(count)
if reps > 0 && count > (2147483647 - consumption) / reps {
bad_format(source)
}
consumption += reps * count
units.push({
atoms,
reps,
explicit_reps,
count,
final_address,
trim_tail: body.length() > 0 && format_space(body[body.length() - 1]),
})
}
{ units, count: consumption, }
}