runtime: Better feedback on UTF-8 errors

This is intended for use by (read-string) which will need to handle
TruncatedInputException by reading more data from the input stream.

- Use separate subclasses for each type of exception. This allows them
  to be handled separately by calling code

- Add validChars() to InvalidByteSequenceException which is the count of
  valid characters before the bad byte sequence. (read-string) will need
  this to calculate the length of the next read

- Remove InvalidHeaderByteException::offsetBy() in favour of merging
  validateChar() in to validateData() and throwing the exception with
  the correct offsets initially
runtime: Implement (read-string)

This is fairly tricky as it tries to use large reads without ever
reading past the end of the requested data. It will calculate the number
of characters remaining and then read that many bytes from the port's
stream. It will then incrementally validate the UTF-8 data and update
the number of valid characters and the offset of the last validated
character. Once the characters are all read or the end of the stream is
reached with will create a StringCell with the contents of the read
buffer.

This is a bit tricky when our read lands in the middle of a UTF-8 byte
sequence. We explicitly catch TruncatedInputException and use that to
update the last validated character offset. This is a bit suboptimal if
we're near the end of the string as we'll only read the number of
characters remaining (which can be as small as 1) instead of the number
of bytes left in the byte sequence.
runtime: (write-string), (flush-output-port)

This exposes StringCell::charRange() to allow direct access to the
appropriate range of UTF-8 inside the StringCell