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

Found

Found : String -> ReadSource

The run before the delimiter, both consumed.

Ended

Ended : String -> ReadSource

The unterminated remainder at the end of input, consumed.

Overflowed

Overflowed : ReadSource

The limit was reached before either; nothing was consumed.

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é"