APIText.Reader
Text.Reader
Reading characters from a byte reader through an encoding.
A text reader decodes a Reader as it reads, in UTF-8 or another
Encoding. A character split across source chunks is completed by
refilling; an incomplete sequence at the end of input is an error, not
the end of input or a replacement character. Reads that can fail on
malformed input perform Fail Encoding.Error; the try operations answer
the error as a value instead. Every read stages its work before consuming,
so a failure, a short exact read, or an overflow consumes nothing.
While a text reader is in use, advance its parent only through it. Leaving
the scope keeps every unconsumed parent byte, lookahead included. Only the
requested prefix is decoded, so malformed text further ahead does not stop
an earlier valid read; a refill may still have grown the parent’s buffer.
Limits
count characters; the byte reader may still prefetch larger chunks, so put
a byte-framed body in Reader.limited first. The Window operations let a
scanner inspect buffered text without consuming it, and commit what it used.
Read
type Read = Found String | Ended String | OverflowedSource
The three distinct answers to a delimited read.
Constructors
Instances:
Eq Read
Show Read
Reader
type Reader eSource
A text reader whose operations perform e.
Window
type WindowSource
An immutable snapshot of a reader’s buffered bytes and a position in it. Inspecting one never refills or consumes; positions derived from it count bytes from the snapshot’s start, as are decoding error offsets. An empty window does not mean the source has ended.
asciiAt
asciiAt : Window -> IntSource
Grammars spelled in ASCII can branch on plain codes: every bundled encoding maps bytes below 128 to the same one-byte scalars. The answer is -1 at the end of the window and -2 for any other byte, valid or not.
atEnd
atEnd : Reader e ->{Fail Error, e} BoolSource
Whether no characters remain.
chunks
chunks : Reader e -> Stream String {Fail Error, e}Source
The rest of the input as a stream of nonempty strings of whole characters. Its boundaries may differ from the source’s chunks. Traversing it advances the reader.
commitWindow
commitWindow : Reader e -> Window ->{e} ()Source
Consumes the reader up to the position, counting only bytes not already committed, so committing the same or an earlier position does nothing. Abandoning a window consumes nothing. Do not advance the reader any other way while using its windows.
matchWindow
matchWindow : Window -> String -> Maybe WindowSource
The position after text when its encoding occurs at the position, or
Nothing on a mismatch, too few buffered bytes, or text the encoding
cannot represent. Empty text matches at once. Only the matched bytes are
validated, so a refusal does not diagnose malformed input; use a
character read for that.
over
over : Reader e -> (Reader {#s} ->{#s} a) ->{e} aSource
Runs action with a UTF-8 text reader over parent.
overWith
overWith : Encoding -> Reader e -> (Reader {#s} ->{#s} a) ->{e} aSource
Runs action with a text reader over parent in encoding. It does not
close the parent.
peekChar
peekChar : Reader e ->{Fail Error, e} Maybe CharSource
The next character without consuming it, or Nothing at the end of
input. It may refill.
peekWindow
peekWindow : Window -> Result Error (Maybe (Char, Int))Source
The character at the position and its encoded width, or Nothing at the
end of the window.
position
position : Reader e ->{e} IntSource
The number of bytes consumed since the reader was created.
readChar
readChar : Reader e ->{Fail Error, e} Maybe CharSource
Consumes and answers the next character, or Nothing at the end of input.
import Fail
import Result exposing (Result(..))
import Text.Reader
Fail.attempt { Text.Reader.withString "ab" { r -> [Text.Reader.readChar r, Text.Reader.readChar r, Text.Reader.readChar r] } } == Ok [Just 'a', Just 'b', Nothing]
readExactly
readExactly : Reader e -> Int ->{Fail Error, e} Maybe StringSource
Consumes and answers exactly count characters, or Nothing, consuming
nothing, when fewer remain. A count of zero or less answers Just "".
import Fail
import Result exposing (Result(..))
import Text.Reader
Fail.attempt { Text.Reader.withString "héllo" { r -> Text.Reader.readExactly r 2 } } == Ok (Just "hé")
Fail.attempt { Text.Reader.withString "hé" { r -> Text.Reader.readExactly r 3 } } == Ok Nothing
readLine
readLine : Reader e -> Int ->{Fail Error, e} ReadSource
Reads a line, dropping its line feed and a carriage return right before it; any other carriage return is content. The limit counts the actual terminator, so CRLF counts as two.
import Fail
import Result exposing (Result(..))
import Text.Reader
Fail.attempt { Text.Reader.withString "a\r\nb" { r -> [Text.Reader.readLine r 10, Text.Reader.readLine r 10] } } == Ok [Text.Reader.Found "a", Text.Reader.Ended "b"]
readUntil
readUntil : Reader e -> String -> Int ->{Fail Error, e} ReadSource
Reads up to delimiter, consuming it without answering it. limit counts
the characters of the run and delimiter together; a limit of zero or less
always overflows, and an empty delimiter is found at once when it is
positive. A delimiter may span source chunks.
import Fail
import Result exposing (Result(..))
import Text.Reader
Fail.attempt { Text.Reader.withString "k=v" { r -> Text.Reader.readUntil r "=" 10 } } == Ok (Text.Reader.Found "k")
Fail.attempt { Text.Reader.withString "kv" { r -> Text.Reader.readUntil r "=" 10 } } == Ok (Text.Reader.Ended "kv")
Fail.attempt { Text.Reader.withString "kv=" { r -> Text.Reader.readUntil r "=" 2 } } == Ok Text.Reader.Overflowed
readUpTo
readUpTo : Reader e -> Int ->{Fail Error, e} StringSource
Consumes and answers at most count characters. It waits for the first
complete character, then answers those already available without waiting
to fill the count. A short answer is not the end of input; an empty one
is, unless count is zero or less.
readWindow
readWindow : Window -> Result Error (Maybe (Char, Int, Window))Source
The character at the position, its encoded width, and the window advanced
past it, or Nothing at the end of the window.
refillWindow
refillWindow : Reader e -> Window ->{e} (Window, Bool)Source
Commits through the position, refills the parent, and answers
(fresh, grew). fresh starts at position zero over the remaining and new
bytes, and later scanning and commits must use it; grew is False only
at the end of the source. An incomplete character stays for the next
chunk. Old windows stay valid for inspection and windowText. A source
failure propagates with the committed prefix consumed.
skipAscii
skipAscii : Window -> WindowSource
Advances one byte past a character asciiAt just answered a code for. It
checks nothing, so used anywhere else the next read may report a
malformed sequence.
spanWindow
spanWindow : Window -> (Char -> Bool) -> Result Error (String, Int, Window)Source
The longest valid prefix at the position whose characters satisfy keep,
with its width in bytes and the position after it. A malformed or
incomplete character ends the prefix; the next read at that position
reports it.
spanWindowUntil
spanWindowUntil : Window -> AsciiSet -> Result Error (String, Int, Window)Source
The span contract of spanWindow for a fixed set of ASCII stops. The run is found and validated in one pass, without a predicate call per byte.
tryPeekChar
tryPeekChar : Reader e ->{e} Result Error (Maybe Char)Source
peekChar, answering a decoding error as a value.
tryReadChar
tryReadChar : Reader e ->{e} Result Error (Maybe Char)Source
readChar, answering a decoding error as a value.
tryReadScalar
tryReadScalar : Reader e ->{e} Result Error (Maybe (Char, Int))Source
Consumes the next character and answers it with its encoded width in
bytes, or Nothing at the end of input. The width is in the reader’s
encoding, which may differ from the character’s UTF-8 width.
tryReadSpan
tryReadSpan : Reader e -> (Char -> Bool) ->{e} Result Error (String, Int)Source
tryReadWhile, also answering the number of bytes consumed in the
reader’s encoding.
tryReadWhile
tryReadWhile : Reader e -> (Char -> Bool) ->{e} Result Error StringSource
Consumes and answers the longest prefix of characters satisfying keep
among those available. It waits for the first complete character, then
stops at the current buffer boundary or the first rejected character,
without pulling more input to extend the prefix. An empty answer means
the end of input or a rejected first character. A valid prefix before
malformed input succeeds; the next call reports the error.
import Fail
import Result exposing (Result(..))
import Text.Reader
Text.Reader.withString "abc1" { r -> Text.Reader.tryReadWhile r { c -> c /= '1' } } == Ok "abc"
window
window : Reader e ->{e} WindowSource
A snapshot of the bytes buffered now, without refilling or consuming.
windowPosition
windowPosition : Window -> IntSource
The position’s distance in bytes from the snapshot’s start.
windowText
windowText : Window -> Window -> Result Error StringSource
The text between two positions derived from the same snapshot, start
first.
withString
withString : String -> (Reader {#s} ->{#s} a) ->{e} aSource
Runs action with a text reader over text in memory. The input cannot
be malformed, but the reads still perform Fail Encoding.Error; handle it
around the read or the whole consumer.
import Fail
import Result exposing (Result(..))
import Text.Reader
Fail.attempt { Text.Reader.withString "héllo" { r -> Text.Reader.readUpTo r 2 } } == Ok "hé"
