///|
priv enum InputMode {
Words
Lines
Delimited(Int32)
}
///|
priv struct ItemReader {
reader : &@io.Reader
mode : InputMode
limit : Int32
mut buffer : Bytes
mut position : Int32
mut ended : Bool
}
///|
fn ItemReader::new(
reader : &@io.Reader,
mode : InputMode,
limit : Int32,
) -> ItemReader {
{ reader, mode, limit, buffer: b"", position: 0, ended: false, }
}
///|
async fn ItemReader::next(self : ItemReader) -> BytesView? {
guard !self.ended else { return None }
let bytes : Array[Byte] = []
let mut quote : Byte? = None
let mut escaped = false
let mut started = false
let literal_delimiter = match self.mode {
Words => None
Lines => Some(10)
Delimited(delimiter) => Some(delimiter)
}
for ;; {
if self.position == self.buffer.length() {
match self.reader.read_some(max_len=65536) {
Some(chunk) => {
self.buffer = chunk
self.position = 0
}
None => {
self.ended = true
if escaped {
raise XargsError("input ends with an unmatched backslash")
}
if quote is Some(delimiter) {
raise XargsError(
"input contains an unmatched '\'\{delimiter.to_int().unsafe_to_char()}\' quote",
)
}
return if started { Some(Bytes::from_array(bytes)) } else { None }
}
}
}
if literal_delimiter is Some(delimiter) {
let start = self.position
let mut end = start
// Preserve the signed-char delimiter boundary. Decode a completed view
// before refilling; only items crossing chunks need an owned accumulator.
while end < self.buffer.length() && self.buffer[end].to_int() != delimiter {
end += 1
}
if end - start > self.limit - bytes.length() {
raise XargsError("input item exceeds \{self.limit} byte limit")
}
self.position = end
if end < self.buffer.length() && bytes.is_empty() {
self.position += 1
return Some(self.buffer[start:end])
}
for byte in self.buffer[start:end] {
bytes.push(byte)
}
if end < self.buffer.length() {
self.position += 1
return Some(Bytes::from_array(bytes))
}
started = started || end > start
continue
}
while self.position < self.buffer.length() {
let byte = self.buffer[self.position]
self.position += 1
if escaped {
bytes.push(byte)
escaped = false
} else if byte == b'\\' && quote != Some(b'\'') {
escaped = true
started = true
} else {
match quote {
Some(delimiter) =>
if byte == delimiter {
quote = None
} else {
bytes.push(byte)
}
None =>
if byte == b'\'' || byte == b'"' {
quote = Some(byte)
started = true
} else if byte is (b' ' | b'\t' | b'\n' | b'\r') {
if started {
return Some(Bytes::from_array(bytes))
}
} else {
bytes.push(byte)
started = true
}
}
}
if bytes.length() > self.limit {
raise XargsError("input item exceeds \{self.limit} byte limit")
}
}
}
}
///|
fn input_text(bytes : BytesView) -> String raise XargsError {
@utf8.decode(bytes) catch {
_ => raise XargsError("input is not valid UTF-8")
}
}