///|
fn hex_digit(value : Int32, upper~ : Bool = false) -> Char {
(if value < 10 {
0x30 + value
} else {
(if upper { 0x41 } else { 0x61 }) + value - 10
}).unsafe_to_char()
}
///|
fn write_hex_byte(
output : StringBuilder,
byte : Byte,
upper~ : Bool = false,
) -> Unit {
output.write_char(hex_digit(byte.to_int() >> 4, upper~))
output.write_char(hex_digit(byte.to_int() & 15, upper~))
}
///|
fn display_byte(byte : Byte, ebcdic : Bool) -> Byte {
let code = byte.to_int()
let code = if !ebcdic {
code
} else {
match code {
129..<=137 => code - 129 + 0x61
145..<=153 => code - 145 + 0x6a
162..<=169 => code - 162 + 0x73
193..<=201 => code - 193 + 0x41
209..<=217 => code - 209 + 0x4a
226..<=233 => code - 226 + 0x53
240..<=249 => code - 240 + 0x30
64 => 32
75 => 46
76 => 60
77 => 40
78 => 43
79 => 124
80 => 38
90 => 33
91 => 36
92 => 42
93 => 41
94 => 59
95 => 126
96 => 45
97 => 47
107 => 44
108 => 37
109 => 95
110 => 62
111 => 63
121 => 96
122 => 58
123 => 35
124 => 64
125 => 39
126 => 61
127 => 34
154 => 94
173 => 91
189 => 93
192 => 123
208 => 125
224 => 92
_ => 46
}
}
if code >= 32 && code < 127 {
code.to_byte()
} else {
b'.'
}
}
///|
fn dump_line(data : ArrayView[Byte], opts : Options, offset : Int64) -> Bytes {
// Display offsets wrap like the provider's unsigned long; input positions stay checked.
let address = (offset.reinterpret_as_uint64() + opts.display_offset).to_string(
radix=16,
)
let prefix = StringBuilder()
for _ in address.length()..<8 {
prefix.write_char('0')
}
prefix.write_string(address)
prefix.write_char(':')
let prefix = @utf8.encode(prefix.to_string())
let line : FixedArray[Byte] = FixedArray::make(
prefix.length() + opts.cols * 10 + 16,
b' ',
)
for index, byte in prefix {
line[index] = byte
}
let group_width = if opts.style is Bits {
8 * opts.group + 1
} else {
2 * opts.group + 1
}
let width = if opts.style is Little {
group_width * ((opts.cols + opts.group - 1) / opts.group)
} else {
(group_width * opts.cols - 1) / opts.group
}
let display = prefix.length() + 3 + width
for position, byte in data {
let index = if opts.style is Little {
position ^ (opts.group - 1)
} else {
position
}
let at = prefix.length() + 1 + group_width * index / opts.group
if opts.style is Bits {
for bit in 0..<8 {
line[at + bit] = if (byte.to_int() & (1 << (7 - bit))) != 0 {
b'1'
} else {
b'0'
}
}
} else {
line[at] = hex_digit(byte.to_int() >> 4, upper=opts.upper)
.to_int()
.to_byte()
line[at + 1] = hex_digit(byte.to_int() & 15, upper=opts.upper)
.to_int()
.to_byte()
}
line[display + position] = display_byte(byte, opts.ebcdic)
}
line[display + data.length()] = b'\n'
Bytes::from_array(line[:display + data.length() + 1].to_owned())
}
///|
fn include_name(path : String, symbol_name : String?) -> String? {
let raw = match symbol_name {
Some(name) => name
None => if path == "-" { return None } else { path }
}
let output = StringBuilder()
if raw.length() > 0 && raw[0] is ('0'..<='9') {
output.write_string("__")
}
for byte in @utf8.encode(raw) {
output.write_char(
if byte is (b'a'..<=b'z' | b'A'..<=b'Z' | b'0'..<=b'9') {
byte.to_int().unsafe_to_char()
} else {
'_'
},
)
}
Some(output.to_string())
}
///|
async fn Output::text(self : Output, text : String) -> Unit {
self.write(@utf8.encode(text))
}
///|
priv struct ZeroLines {
mut count : Int32
mut second : Bytes
mut last : Bytes
}
///|
fn ZeroLines::line(
self : ZeroLines,
output : @buffer.Buffer,
line : Bytes,
zero : Bool,
) -> Unit {
if zero {
if self.count == 0 {
output.write_bytes(line[:])
} else if self.count == 1 {
self.second = line
}
self.last = line
if self.count < 4 {
self.count += 1
}
} else {
if self.count == 2 {
output.write_bytes(self.second[:])
} else if self.count > 2 {
output.write_bytes(b"*\n"[:])
}
output.write_bytes(line[:])
self.count = 0
}
}
///|
fn ZeroLines::finish(self : ZeroLines, output : @buffer.Buffer) -> Unit {
if self.count == 2 {
output.write_bytes(self.second[:])
} else if self.count == 3 {
output.write_bytes(self.second[:])
output.write_bytes(self.last[:])
} else if self.count > 3 {
output.write_bytes(b"*\n"[:])
output.write_bytes(self.last[:])
}
}
///|
async fn dump_stream(
reader : &@io.Reader,
output : Output,
opts : Options,
start_offset : Int64,
path : String,
positioned_file~ : @fs.File? = None,
) -> Unit {
let pending : Array[Byte] = []
let zero_lines : ZeroLines = { count: 0, second: b"", last: b"", }
let mut offset = start_offset
let mut consumed = 0L
let mut column = 0
let name = if opts.style is Include {
include_name(path, opts.name)
} else {
None
}
if name is Some(name) {
output.text("unsigned char " + name + "[] = {\n")
}
let buffer : FixedArray[Byte] = FixedArray::make(65536, 0)
while opts.limit != Some(consumed) {
let maximum = match opts.limit {
Some(limit) => (limit - consumed).min(65536L).to_int()
None => 65536
}
let count = match positioned_file {
Some(file) =>
file.read_at(
buffer,
position=checked_add(
start_offset, consumed, 4, "xxd: input offset overflow.",
),
len=maximum,
)
None => reader.read(buffer, max_len=maximum)
}
if count == 0 {
break
}
let text = StringBuilder()
let formatted = @buffer.Buffer()
for index in 0.. 0 {
column += 1
if column == opts.cols {
text.write_char('\n')
column = 0
}
}
} else if opts.style is Include {
text.write_string(
if column == 0 {
if consumed == 0L && index == 0 {
" "
} else {
",\n "
}
} else {
", "
},
)
text.write_string(if opts.upper { "0X" } else { "0x" })
write_hex_byte(text, byte, upper=opts.upper)
column = (column + 1) % opts.cols
} else {
pending.push(byte)
if pending.length() == opts.cols {
zero_lines.line(
formatted,
dump_line(pending, opts, offset),
opts.autoskip && pending.all(byte => byte == b'\x00'),
)
pending.clear()
offset = checked_add(
offset,
opts.cols.to_int64(),
4,
"xxd: input offset overflow.",
)
}
}
}
consumed = checked_add(
consumed,
count.to_int64(),
4,
"xxd: input offset overflow.",
)
if opts.style is Plain || opts.style is Include {
output.text(text.to_string())
} else {
output.write(formatted.to_bytes())
}
}
match opts.style {
Plain => if column != 0 || opts.cols == 0 { output.write(b"\n") }
Include => {
if consumed > 0L {
output.write(b"\n")
}
if name is Some(name) {
output.text(
"};\nunsigned int " + name + "_len = " + consumed.to_string() + ";\n",
)
}
}
_ => {
let formatted = @buffer.Buffer()
if pending.is_empty() {
zero_lines.finish(formatted)
} else {
zero_lines.line(formatted, dump_line(pending, opts, offset), false)
}
output.write(formatted.to_bytes())
}
}
}