+ #
+ # Returns unicode replacement character '�' if an
+ # invalid byte sequence is read.
+ #
+ # `read_char` may block if:
+ #
+ # * No byte could be read from the current buffer
+ # * An incomplete char is partially read, and more bytes are
+ # required for full decoding.
+ fun read_char: nullable Char do
+ if eof then return null
+ var cod = codec
+ var codet_sz = cod.codet_size
+ var lk = lookahead
+ var llen = lookahead_length
+ if llen < codet_sz then
+ llen += raw_read_bytes(lk.fast_cstring(llen), codet_sz - llen)
+ end
+ if llen < codet_sz then
+ lookahead_length = 0
+ return 0xFFFD.code_point
+ end
+ var ret = cod.is_valid_char(lk, codet_sz)
+ var max_llen = cod.max_lookahead
+ while ret == 1 and llen < max_llen do
+ var rd = raw_read_bytes(lk.fast_cstring(llen), codet_sz)
+ if rd < codet_sz then
+ llen -= codet_sz
+ if llen > 0 then
+ lookahead.lshift(codet_sz, llen, codet_sz)
+ end
+ lookahead_length = llen.max(0)
+ return 0xFFFD.code_point
+ end
+ llen += codet_sz
+ ret = cod.is_valid_char(lk, llen)
+ end
+ if ret == 0 then
+ var c = cod.decode_char(lk)
+ var clen = c.u8char_len
+ llen -= clen
+ if llen > 0 then
+ lookahead.lshift(clen, llen, clen)
+ end
+ lookahead_length = llen
+ return c
+ end
+ if ret == 2 or ret == 1 then
+ llen -= codet_sz
+ if llen > 0 then
+ lookahead.lshift(codet_sz, llen, codet_sz)
+ end
+ lookahead_length = llen
+ return 0xFFFD.code_point
+ end
+ # Should not happen if the decoder works properly
+ var arr = new Array[Object]
+ arr.push "Decoder error: could not decode nor recover from byte sequence ["
+ for i in [0 .. llen[ do
+ arr.push lk[i]
+ arr.push ", "
+ end
+ arr.push "]"
+ var err = new IOError(arr.plain_to_s)
+ err.cause = last_error
+ last_error = err
+ return 0xFFFD.code_point
+ end