Repository navigation
roc-parser 2.0.0
2.0.0 is a redesign. Every module has a new API that follows the Roc
language reference: type modules, methods called with a dot, Try with an
open error tag named after the format, and Try(_, [Missing]) for optional
values. CSV rows and YAML documents can now be decoded straight into your own
record types with parser_for. Every format parser was also rewritten to
follow its specification and checked against a mature reference
implementation, so many inputs now parse differently.
Code written for 1.x does not compile against 2.0.0 without changes. Start
with the upgrade guide below, then read the behaviour changes, which can
affect your data even where your code compiles unchanged.
Upgrade guide
Each table maps a 1.x name to its 2.0.0 replacement. A dash means the item
was removed; the note says what to use instead.
Parser
| 1.x | 2.0.0 |
|---|---|
ParseResult(input, a) : Try({ val, input }, [ParsingFailure(Str)]) |
ParseResult(input, a) : Try({ value, rest }, [ParseError({ message, offset })]) |
build_primitive_parser(fun) |
Parser.custom(fun), where fun returns Ok({ value, rest }) or Err(ParseError({ message, offset })) |
parse_partial(p, input) |
p.run(input) |
parse(p, input, is_done) |
p.parse(input): the completion predicate is gone; leftover input is a ParseError |
[ParsingFailure(Str), ParsingIncomplete(input)] |
[ParseError({ message : Str, offset : U64 })] |
apply(fun_parser, arg_parser) |
fun_parser.keep(arg_parser) |
sep_by1 |
sep_by_one_or_more |
maybe(p) : Parser(input, Try(a, [Nothing])) |
p.maybe() : Parser(input, Try(a, [Missing])) |
Internal and_then |
p.and_then(fun), now public and documented |
fail, custom, run, parse, one_of, flatten, many, one_or_more,
sep_by and sep_by_one_or_more carry where [input.len : input -> U64].
List(U8) and every input type in this package satisfy it; a custom input
type must provide len.
String is now Utf8
| 1.x | 2.0.0 |
|---|---|
import parser.String |
import parser.Utf8 |
String.Utf8 (the input type) |
Utf8.Bytes, still List(U8) |
String.parse_str |
Utf8.parse_str |
String.parse_str_partial, returning { val, input } |
Utf8.parse_str_partial, returning { value, rest } |
String.parse_utf8 |
Utf8.parse_bytes |
String.parse_utf8_partial |
Utf8.parse_bytes_partial, returning { value, rest } |
[ParsingFailure(Str), ParsingIncomplete(...)] |
[ParseError({ message : Str, offset : U64 })] |
String.any_thing |
Utf8.rest |
String.any_string |
Utf8.rest_str |
String.one_of |
- (use Parser.one_of) |
String.str_from_utf8 |
- (use Str.from_utf8 or Str.from_utf8_lossy) |
String.str_from_ascii |
- (it crashed on non-ASCII bytes; use Str.from_utf8_lossy) |
codeunit, codeunit_satisfies, utf8, string, any_codeunit, digit
and digits keep their names.
CSV
| 1.x | 2.0.0 |
|---|---|
CSV (opaque), CSVRecord, CSVField |
CSV.Record : List(List(U8)) |
parse_str(p, text) with [ParsingFailure(Str), SyntaxError(Str), ParsingIncomplete(CSVRecord)] |
CSV.parse_with(p, text) with [InvalidCsv(CSV.Error)] |
parse_str_to_csv(text) |
CSV.parse_records(text) : Try(List(Record), [InvalidCsv(Error)]) |
parse_csv(p, csv) |
CSV.decode(p, records) |
parse_str_to_csv_record, parse_csv_record |
- (use CSV.parse_records, then CSV.decode or p.parse(record)) |
file |
CSV.parser : Parser(Utf8.Bytes, List(Record)) |
field : Parser(String.Utf8, a) -> Parser(CSVRecord, a) |
CSV.field : Parser(Utf8.Bytes, a) -> Parser(Record, a) |
CSV.Error is { record, field, line, column, message }. record, string,
u64 and f64 keep their names.
HTTP
| 1.x | 2.0.0 |
|---|---|
Header : [Header(Str, Str)] |
Header : { name : Str, value : Str }, with HTTP.header(headers, name) for a case-insensitive lookup |
HttpVersion |
Version : { major : U8, minor : U8 } |
Request.uri |
Request.target |
Request.http_version, Response.http_version |
.version |
Response.status |
Response.reason |
Method |
Method, plus Extension(Str) for any other method token |
String.parse_str(HTTP.request, text) |
HTTP.parse_request(bytes) : Try({ request, rest }, [InvalidHttp(Error)]) |
String.parse_str(HTTP.response, text) |
HTTP.parse_response(bytes) : Try({ response, rest }, [InvalidHttp(Error)]) |
HTTP.Error is { offset, message }. HTTP.request and HTTP.response
remain as parsers for embedding; their failures now carry the byte offset of
the problem.
Markdown
| 1.x | 2.0.0 |
|---|---|
String.parse_str(Markdown.all, text) |
Markdown.parse_str(text) : List(Markdown); parsing cannot fail |
String.parse_str(Markdown.inlines, text) |
Markdown.parse_inlines(text) : List(Inline) |
Markdown.all, Markdown.inlines (parsers) |
Markdown.parser, Markdown.inline_parser |
Frontmatter({ raw : Str }) |
Frontmatter(Str), and Markdown.frontmatter(blocks) : Try(Str, [Missing]) |
LinkTarget.title : [Some(Str), None] |
LinkTarget.title : Try(Str, [Missing]) |
TODO(Str) variant |
- (never produced by the CommonMark parser) |
Markdown.heading, link, image, code |
- (parse the document or the inlines and match on the tree) |
to_debug_str, inline_to_debug_str |
- (use Str.inspect) |
Xml
| 1.x | 2.0.0 |
|---|---|
xml_parser |
Xml.parse_str(text) : Try(Xml, [InvalidXml(Xml.Error)]), or Xml.parser to embed |
xml_declaration : [Given(Declaration), Missing] |
declaration : Try(Declaration, [Missing]) |
Declaration.encoding : [Given(TextEncoding), Missing] |
encoding : Try(TextEncoding, [Missing]) |
Version (opaque, Version.new(after_dot)) |
Version : { major : U64, minor : U64 } |
Element(name, attributes, children) |
Element({ name, attributes, children }) |
Xml.Error is { line, column, message }.
Yaml
| 1.x | 2.0.0 |
|---|---|
String(Str) variant |
Text(Str) |
YamlError(Yaml.Error) |
InvalidYaml(Yaml.Error) |
Breaking changes
One error convention
Every format reports invalid input with an open tag named after the format,
holding a record with a message and the most precise location the format
has:
InvalidCsv({ record, field, line, column, message })InvalidYaml({ line, column, message })InvalidXml({ line, column, message })InvalidHttp({ offset, message })
Combinator runners (Utf8.parse_str, parse_bytes and the partial runners,
Parser.parse and Parser.run) return ParseError({ message, offset }).
The offset is the byte offset of the furthest failure: the failure that
read furthest into the input, even when a repetition stopped at it or an
alternative recovered from it. A bad element in the middle of many or
sep_by is reported as its own failure rather than as leftover input, and
leftover input after a successful parse is unexpected input at the
leftover. Because every error is an open tag union, ? composes them in one
function without map_err.
Messages quote at most 32 bytes of the input, decoded lossily, so they stay
short and cannot crash on bytes that are not UTF-8. Utf8.codeunit,
Utf8.codeunit_satisfies, Utf8.utf8 and Utf8.string no longer quote the
input at all ("expected char ," rather than "expected char , but found
x. While reading: ..."): their messages are built once, when the parser is
made, because building one on every failure made alternatives and
repetitions several times slower. The offset says where the failure is.
Utf8.digits followed by leftover input reports unexpected input at the
leftover. alt keeps one failure instead of joining messages with "or".
Code that matches on message text may need updating.
Optional values are Try(_, [Missing])
Parser.maybe, the XML declaration and its encoding, and Markdown link
titles all use Try(a, [Missing]), as the language reference recommends.
The same type marks optional fields for type-directed decoding.
Removed
Parser.apply,build_primitive_parser,parse_partial,sep_by1.String(nowUtf8),String.one_of,str_from_utf8,str_from_ascii.- The opaque
CSVtype,CSVRecord,CSVField,parse_csv,
parse_csv_record,parse_str_to_csv,parse_str_to_csv_record,file,
and theSyntaxError,ParsingFailureandParsingIncompleteerrors. HTTP.HttpVersionand theHeader(name, value)tag.Markdown.all,inlines,heading,link,image,code,
to_debug_str,inline_to_debug_strand theTODOvariant.Xml.xml_parser,Version.newand theGiventag.Yaml'sStringvariant andYamlError.
Utf8, CSV and HTTP are void modules: opaque types backed by [], as the
language reference recommends for modules that hold no values of their own.
New dependency
roc-parser now depends on
roc-lang/unicode 4.2.0, for the
Unicode data CommonMark needs (punctuation and whitespace categories, case
folding). Roc fetches it with the package; no code change is needed.
New features
Type-directed decoding with parser_for
-
CSV.parse(text)decodes a file with a header row into a list of records.
The row type comes from your annotation: each field reads the column with
the same name, aTry(a, [Missing])field isErr(Missing)when its column
is absent, and aTry(a, [Null])field isErr(Null)when its cell is
empty. Cells decode asStr,Bool, any integer type,Dec,F32,F64
and tags without payloads. -
CSV.parse_normalizedmatches headers such asFirst Nameorfirst-name
to a fieldfirst_name, andCSV.parse_headerlessdecodes rows without a
header into tuples. -
Yaml.decode(text)decodes a document into a record, list, dict or tuple.
Plain scalars are resolved for the target type, soversion: 1.10decodes
into aStrfield as written.Yaml.decodertakes options for kebab-case
or camelCase keys and for rejecting unknown keys (by default they are
skipped). -
A required field that is absent fails with
MissingRequiredField(name),
which the compiler adds to the error type, so annotations name it:people : Try(List({ name : Str, age : U64 }), [InvalidCsv(CSV.Error), MissingRequiredField(Str)]) people = CSV.parse("name,age\nAda,36\n")
-
CSV.ParseableandYaml.Parseableare where-clause aliases for writing
your own generic functions over decodable types.
Other additions
CSV.split_headerseparates a header row from raw records.HTTP.header(headers, name)looks a header up case-insensitively.HTTP.parse_requestandHTTP.parse_responsetakeList(U8)and return
the message with therestof the bytes, which is the next pipelined
message.HTTP.parse_requestsreads a whole pipeline.HTTP.MethodgainsExtension(Str)for any RFC 9110 method token.Markdown.parse_str,parse_inlines,parser,inline_parserand
Markdown.frontmatter.Xml.parse_str, with a one-based line and byte column on every error, and
the node methodsname,attribute,children_namedandtext.Yaml.parserand the tree methodsget,at,get_path,as_str,
as_i64,as_boolandas_list.to_hashon theMarkdown,XmlandYamltree types, so trees can be
Dictkeys orSetmembers.Yaml.to_inspectescapes control characters.Parser.spanruns a parser and returns the input it consumed, as a slice.Utf8.ByteClass(from_bytes,from_predicate,complement,
contains) withUtf8.find_any,Utf8.skip_class,Utf8.find_line_end
and theUtf8.span_classparser find or skip runs of bytes 16 at a time
with SIMD instructions.
Behaviour changes
CSV
- The dialect is RFC 4180 with the relaxations of Python's
csvmodule
(strict mode). A final line break no longer starts an extra record, and
empty input has no records instead of[[""]]. A lone CR separates records,
alongside LF and CRLF. - Fields may hold any UTF-8, tabs, NUL and control characters, and a
"that
is not at the start of a field is an ordinary character. These were syntax
errors. A blank line is a record with one empty field. - A quoted field followed by anything other than
,, a line break or the
end of input is an error. CSV.u64andCSV.f64use a data grammar instead of Roc literal syntax:
1_000,0x10and-0are no longerU64values,1_0.5and0x1p3are
no longerF64values, andF64acceptsinf,infinityandnanin any
case, with1e400saturating to infinity instead of failing.- With
CSV.parse_withandCSV.decode, a record with fields the parser did
not read is anInvalidCsverror naming the record. WithCSV.parse, every
record must have exactly as many fields as the header; blank lines are
skipped.
HTTP
- Requests and responses are parsed per RFC 9112 and RFC 9110. The body is
delimited byContent-Lengthor decoded fromchunkedcoding, and the
bytes after the message are left for the next pipelined message. Before,
every remaining byte became the body. - To rule out request smuggling, these are rejected: both
Content-Length
andTransfer-Encoding; conflicting, malformed or overflowing
Content-Length; any transfer coding other than a singlechunked;
Transfer-Encodingin HTTP/1.0; a missing or duplicateHostin HTTP/1.1;
whitespace before a field's colon; obs-fold; bare CR or LF line endings; and
control characters in field values. - Status codes are exactly three digits, 100 to 999 (before,
65736was read
as 200), and an empty reason phrase is allowed. The version is
DIGIT "." DIGIT, field names must be tokens, field values are trimmed of
surrounding whitespace, and request targets must be visible ASCII. - Any method token is accepted, as
Extension(name)when it is not one of
the standard methods. Method names are case-sensitive, sogetis
Extension("get").
YAML
- Plain scalars resolve with the YAML 1.2 core schema. Only
null,Null,
NULLand~are null and onlytrue/True/TRUEand
false/False/FALSEare booleans, sonUlLandtRUEare now text.
0ooctal,0xhexadecimal,.infand.nanare numbers, and values such
as1.2.3or1eare text instead of errors. - Literal and folded block scalars (
|,>, with chomping and indentation
indicators), all double-quoted escapes, nested compact sequences (- - x)
and sequences at their key's indentation (steps:\n- a) are supported. - Invalid YAML that was accepted, sometimes as a wrong value, is rejected:
characters outside YAML's printable set, block collections on the---
line, plain scalars starting with an indicator or containing:, text
after a closing quote, and keys starting with anchors, tags or other
indicators. A leading byte order mark is dropped. - Quotes and brackets inside plain scalars are text (
k: it's # commentis
it's), a lone CR is a line break, and flow collections count toward the
100-level nesting limit. - Error columns count bytes and point at the value itself rather than the
space before it: an unterminated flow collection atport: [8080is now
reported at column 7, not 6.
XML
- Documents are checked for XML 1.0 well-formedness: end tags must match
their start tags, and<or a bare&in attribute values,]]>and
control characters in text, duplicate attributes, and attributes without
whitespace between them are rejected. - Names may contain digits and non-ASCII letters (
<h1>parses). Entity and
character references are decoded, and line endings and attribute values are
normalised as the specification requires. - Comments and processing instructions are accepted wherever the
specification allows and dropped from the tree. A DOCTYPE is rejected as
unsupported. - Encoding names are compared case-insensitively, so
encoding="utf-8"is
Utf8Encoding, and any1.xversion is accepted.
Markdown
- The parser was rebuilt on the CommonMark 0.31.2 parsing strategy, with the
GitHub Flavored Markdown tables, task lists, strikethrough and extended
autolinks. Against the CommonMark spec examples it now agrees with
cmark-gfm on block structure apart from documented oracle quirks; many
documents give a different, specification-conforming tree. - An unclosed code fence runs to the end of the document, changing the bullet
character starts a new list, and a pipe inside a table cell's code span must
be escaped as\|. - A soft line break inside a paragraph is kept as
"\n"insideText. - Block quotes and lists nest at most 1,000 levels deep, and so do emphasis,
strikethrough, links and images; deeper markers, delimiters and brackets
are read as text, so very deep input no longer overflows the stack. - Emphasis follows the delimiter-run rules, links and images support inline,
full, collapsed and shortcut references, HTML5 named and numeric character
references are decoded, whitespace inside<...>destinations is trimmed,
and GFMwww.autolinks get anhttp://prefix. - Raw HTML and link destinations pass through unfiltered. Sanitize them
before rendering untrusted Markdown to HTML.
Fixes
- Crashes: a non-UTF-8 byte in an HTTP header value or request target, a
non-UTF-8 CSV field in an error message, and an unsupported YAML escape
before a multi-byte character no longer abort the process. - Hangs:
many,one_or_moreandsep_bystop when an element succeeds
without consuming input, instead of looping forever. - Wrong results: YAML quoted scalars end at the real closing quote, and
- key: valuetakes its indentation from the content; XML<a></b>is
rejected; Markdown keeps\vand\fat line ends. - Quadratic cases, now linear: failure messages that quoted the whole
remaining input; XML duplicate-attribute checks and sibling lists; YAML
documents with many block scalars, keys or compact sequence entries, and
deeply nested flow collections; Markdown pathological inline patterns, long
blocks and lists, and deep container nesting.
Performance
The quadratic cases listed under Fixes are gone. The CSV, HTTP and XML
parsers scan with SIMD (Utf8.ByteClass), and XML cuts names, text and
attribute values from the validated input string instead of validating each
one. The
Performance chapter
compares the parsers with Go, Rust and Python libraries, states the
complexity guarantees and the known super-linear cases, and gives tips; the
benchmark harness and comparators are described in the Benchmarks chapter.
Documentation and tooling
- A new manual covers each format, error reporting, choosing an approach,
the design's background, compatibility, conformance, performance,
contributing, property testing and maintenance. Every Roc example in it is
compiled, tested and run in CI. - The documentation site moved. GitHub Pages now serves only the current
release's manual and API reference, rebuilt from each release, so links to
the old/<version>/pages no longer work. Each release carries its own
manual (HTML and PDF) and API reference as release assets. - Property-test targets built on
roc-fuzz 0.4.2 cover every
module: raw-input targets with metamorphic relations, structured generators
that build the expected result from the specification, and targets that
inject known violations (malformed XML, HTTP request smuggling). CI runs a
smoke test of each on every pull request and longer scheduled campaigns. - Conformance reviews compare each format with a pinned reference
implementation and its test suite: Python'scsvmodule, expat and the W3C
XML Conformance Test Suite, ruamel.yaml and the yaml-test-suite, h11, and
cmark-gfm with markdown-it-py on the CommonMark spec examples. Every allowed
disagreement is recorded in a baseline with a reason. - The examples in
examples/now use the package source. Each release
attaches them asroc-parser-examples-<version>.zip, pinned to its bundle,
instead of a follow-up pull request rewriting their URLs. - A benchmark harness compares the parsers with Go, Rust and Python
libraries, weekly and on demand.
Thanks
Thanks to @albx79 for the block scalar parsing
in #80 that
started the YAML work in this release.
Using this release
Built and tested with Roc nightly-2026-09-29-7f11a82 (https://github.com/roc-lang/nightlies/releases/tag/nightly-2026-09-29-7f11a82); install that nightly to use it.
app [main!] {
# your platform here
parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/2.0.0/7CLzCK6qUz7zmj6nvBxMEFu11HPwQTnCovKiyWzDSLTW.tar.zst",
}Roc API changes
Comparing https://github.com/lukewilliamboswell/roc-parser/releases/download/1.2.0/GzeZxk7V7GHFa42qhgzd8gUgX6cEyY3NmrwmDfsuskNd.tar.zst (old, 1.2.0) with package/main.roc (new)...
---- CSV - MAJOR ----
- CSV.CSVField : (alias for String.Utf8)
- CSV.CSVRecord : (alias for List(CSV.CSVField))
+ CSV.Error : (alias for { column : Num.U64, field : Num.U64, line : Num.U64, message : Str, record : Num.U64 })
+ CSV.Format : (opaque type)
+ CSV.Format.invalid_value : CSV.Format, CSV.State -> [InvalidCsv({ column : Num.U64, field : Num.U64, line : Num.U64, message : Str, record : Num.U64 })]a
+ CSV.Format.parse_bool : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Bool }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_dec : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.Dec }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_f32 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.F32 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_f64 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.F64 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_i128 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.I128 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_i16 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.I16 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_i32 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.I32 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_i64 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.I64 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_i8 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.I8 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_null : CSV.Format, CSV.State -> Try(CSV.State, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_record_after_field : CSV.Format, CSV.State -> Try([Continue(CSV.State), Done(CSV.State)]a, [InvalidCsv(CSV.Error)]b)
+ CSV.Format.parse_record_field : CSV.Format, Encoding.FieldName.FieldNames(_shape), CSV.State -> Try([Continue(CSV.State), Done(CSV.State), Field({ field : Encoding.FieldName(_shape), rest : CSV.State }), TryField({ name : Str, rest : CSV.State }), TryFieldCaseless({ name : Str, rest : CSV.State })]b, [InvalidCsv(CSV.Error)]c)
+ CSV.Format.parse_record_start : CSV.Format, CSV.State -> Try([Counted({ len : Num.U64, rest : CSV.State }), Uncounted(CSV.State)]a, [InvalidCsv(CSV.Error)]b)
+ CSV.Format.parse_str : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Str }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_tag_union : CSV.Format, Encoding.ParseTagUnionSpec(a), CSV.State -> Try({ rest : CSV.State, value : a }, [InvalidCsv({ column : Num.U64, field : Num.U64, line : Num.U64, message : Str, record : Num.U64 })]b)
+ CSV.Format.parse_tuple_end : CSV.Format, CSV.State, Num.U64 -> Try(CSV.State, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_tuple_next : CSV.Format, CSV.State, Num.U64, Num.U64 -> Try(CSV.State, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_tuple_start : CSV.Format, CSV.State, Num.U64 -> Try(CSV.State, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_u128 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.U128 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_u16 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.U16 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_u32 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.U32 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_u64 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.U64 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.parse_u8 : CSV.Format, CSV.State -> Try({ rest : CSV.State, value : Num.U8 }, [InvalidCsv(CSV.Error)]a)
+ CSV.Format.rename_field : CSV.Format, Str -> Str
+ CSV.Format.skip_record_field : CSV.Format, CSV.State -> Try(CSV.State, [InvalidCsv(CSV.Error)]a)
+ CSV.Record : (alias for List(List(Num.U8)))
+ CSV.State : (opaque type)
+ CSV.decode : Parser(CSV.Record, a), List(CSV.Record) -> Try(List(a), [InvalidCsv(CSV.Error)]b)
- CSV.field : Parser(String.Utf8, a) -> Parser(List(CSV.CSVField), a)
+ CSV.field : Parser(Utf8.Bytes, a) -> Parser(List(List(Num.U8)), a)
- CSV.file : Parser(String.Utf8, CSV)
- CSV.is_eq : a
+ CSV.parse : Str -> Try(List(row), [InvalidCsv(CSV.Error)]errs) where row.parser_for : CSV.Format -> (CSV.State -> Try({ rest : CSV.State, value : row }, [InvalidCsv(CSV.Error)]errs))
- CSV.parse_csv : Parser(CSV.CSVRecord, a), CSV -> Try(List(a), [ParsingFailure(Str), ParsingIncomplete(List(CSV.CSVField))]b)
- CSV.parse_csv_record : Parser(CSV.CSVRecord, a), CSV.CSVRecord -> Try(a, [ParsingFailure(Str), ParsingIncomplete(List(CSV.CSVField))]b)
+ CSV.parse_headerless : Str -> Try(List(row), [InvalidCsv(CSV.Error)]errs) where row.parser_for : CSV.Format -> (CSV.State -> Try({ rest : CSV.State, value : row }, [InvalidCsv(CSV.Error)]errs))
+ CSV.parse_normalized : Str -> Try(List(row), [InvalidCsv(CSV.Error)]errs) where row.parser_for : CSV.Format -> (CSV.State -> Try({ rest : CSV.State, value : row }, [InvalidCsv(CSV.Error)]errs))
+ CSV.parse_records : Str -> Try(List(CSV.Record), [InvalidCsv(CSV.Error)]a)
- CSV.parse_str : Parser(CSV.CSVRecord, a), Str -> Try(List(a), [ParsingFailure(Str), ParsingIncomplete(CSV.CSVRecord), SyntaxError(Str)]b)
- CSV.parse_str_to_csv : Str -> Try(CSV, [ParsingFailure(Str), ParsingIncomplete(List(Num.U8))]a)
- CSV.parse_str_to_csv_record : Str -> Try(List(CSV.CSVField), [ParsingFailure(Str), ParsingIncomplete(List(Num.U8))]a)
+ CSV.parse_with : Parser(CSV.Record, a), Str -> Try(List(a), [InvalidCsv(CSV.Error)]b)
+ CSV.parser : Parser(List(Num.U8), List(CSV.Record))
- CSV.record : a -> Parser(CSV.CSVRecord, a)
+ CSV.record : a -> Parser(CSV.Record, a)
+ CSV.split_header : List(CSV.Record) -> { header : List(Str), rows : List(List(List(Num.U8))) }
---- HTTP - MAJOR ----
+ HTTP.Error : (alias for { message : Str, offset : Num.U64 })
- HTTP.Header : (alias for [Header(Str, Str)]#polarity)
+ HTTP.Header : (alias for { name : Str, value : Str })
- HTTP.HttpVersion : (alias for { major : Num.U8, minor : Num.U8 })
- HTTP.Method : (alias for [Connect, Delete, Get, Head, Options, Patch, Post, Put, Trace]#polarity)
+ HTTP.Method : (alias for [Connect, Delete, Extension(Str), Get, Head, Options, Patch, Post, Put, Trace]#polarity)
- HTTP.Request : (alias for { body : List(Num.U8), headers : List(HTTP.Header), http_version : HTTP.HttpVersion, method : HTTP.Method, uri : Str })
+ HTTP.Request : (alias for { body : List(Num.U8), headers : List(HTTP.Header), method : HTTP.Method, target : Str, version : HTTP.Version })
- HTTP.Response : (alias for { body : List(Num.U8), headers : List(HTTP.Header), http_version : HTTP.HttpVersion, status : Str, status_code : Num.U16 })
+ HTTP.Response : (alias for { body : List(Num.U8), headers : List(HTTP.Header), reason : Str, status_code : Num.U16, version : HTTP.Version })
+ HTTP.Version : (alias for { major : Num.U8, minor : Num.U8 })
+ HTTP.header : List({ name : Str, value : Str }), Str -> Try(Str, [Missing]a)
+ HTTP.parse_request : List(Num.U8) -> Try({ request : HTTP.Request, rest : List(Num.U8) }, [InvalidHttp(HTTP.Error)]a)
+ HTTP.parse_requests : List(Num.U8) -> Try(List(HTTP.Request), [InvalidHttp(HTTP.Error)]a)
+ HTTP.parse_response : List(Num.U8) -> Try({ response : HTTP.Response, rest : List(Num.U8) }, [InvalidHttp(HTTP.Error)]a)
- HTTP.request : Parser(String.Utf8, { body : List(Num.U8), headers : List(HTTP.Header), http_version : HTTP.HttpVersion, method : HTTP.Method, uri : Str })
+ HTTP.request : Parser(List(Num.U8), HTTP.Request)
- HTTP.response : Parser(List(Num.U8), { body : List(Num.U8), headers : List(HTTP.Header), http_version : HTTP.HttpVersion, status : Str, status_code : Num.U16 })
+ HTTP.response : Parser(List(Num.U8), HTTP.Response)
---- Markdown - MAJOR ----
- Markdown : := [Blockquote(List(Markdown)), Code({ info : Str, pre : Str }), Frontmatter({ raw : Str }), Heading({ content : List(Markdown.Inline), level : Markdown.Level }), HtmlBlock(Str), ListBlock({ items : List({ blocks : List(Markdown), task : Markdown.TaskState }), kind : Markdown.ListKind, loose : Bool }), Paragraph(List(Markdown.Inline)), TODO(Str), Table({ align : List(Markdown.Alignment), header : List(List(Markdown.Inline)), rows : List(List(List(Markdown.Inline))) }), ThematicBreak]
+ Markdown : := [Blockquote(List(Markdown)), Code({ info : Str, pre : Str }), Frontmatter(Str), Heading({ content : List(Markdown.Inline), level : Markdown.Level }), HtmlBlock(Str), ListBlock({ items : List({ blocks : List(Markdown), task : Markdown.TaskState }), kind : Markdown.ListKind, loose : Bool }), Paragraph(List(Markdown.Inline)), Table({ align : List(Markdown.Alignment), header : List(List(Markdown.Inline)), rows : List(List(List(Markdown.Inline))) }), ThematicBreak]
+ Markdown.Alignment.to_hash : a
+ Markdown.Inline.to_hash : a
+ Markdown.Level.to_hash : a
- Markdown.LinkTarget : (alias for { href : Str, title : [None, Some(Str)]#polarity })
+ Markdown.LinkTarget : (alias for { href : Str, title : Try(Str, [Missing]#polarity) })
+ Markdown.ListKind.to_hash : a
+ Markdown.TaskState.to_hash : a
- Markdown.all : Parser(String.Utf8, List(Markdown))
- Markdown.code : Parser(List(Num.U8), Markdown)
+ Markdown.frontmatter : List(Markdown) -> Try(Str, [Missing]a)
- Markdown.heading : Parser(List(Num.U8), Markdown)
- Markdown.image : Parser(List(Num.U8), Markdown.Inline)
+ Markdown.inline_parser : Parser(List(Num.U8), List(Markdown.Inline))
- Markdown.inline_to_debug_str : Markdown.Inline -> Str
- Markdown.inlines : Parser(String.Utf8, List(Markdown.Inline))
- Markdown.link : Parser(List(Num.U8), Markdown.Inline)
+ Markdown.parse_inlines : Str -> List(Markdown.Inline)
+ Markdown.parse_str : Str -> List(Markdown)
+ Markdown.parser : Parser(List(Num.U8), List(Markdown))
- Markdown.to_debug_str : Markdown -> Str
+ Markdown.to_hash : a
---- Parser - MAJOR ----
- Parser.ParseResult : (alias for Try({ input : input, val : a }, [ParsingFailure(Str)]#polarity))
+ Parser.ParseResult : (alias for Try({ rest : input, value : a }, [ParseError({ message : Str, offset : Num.U64 })]#polarity))
+ Parser.and_then : Parser(input, a), (a -> Parser(input, b)) -> Parser(input, b)
- Parser.apply : Parser(input, a -> b), Parser(input, a) -> Parser(input, b)
- Parser.build_primitive_parser : (input -> Parser.ParseResult(input, a)) -> Parser(input, a)
+ Parser.custom : (input -> Parser.ParseResult(input, a)) -> Parser(input, a) where input.len : input -> Num.U64
- Parser.fail : Str -> Parser(input, a)
+ Parser.fail : Str -> Parser(input, a) where input.len : input -> Num.U64
- Parser.flatten : Parser(input, Try(a, Str)) -> Parser(input, a)
+ Parser.flatten : Parser(input, Try(a, Str)) -> Parser(input, a) where input.len : input -> Num.U64
- Parser.many : Parser(input, a) -> Parser(input, List(a))
+ Parser.many : Parser(input, a) -> Parser(input, List(a)) where input.len : input -> Num.U64
- Parser.maybe : Parser(input, a) -> Parser(input, Try(a, [Nothing]c))
+ Parser.maybe : Parser(input, a) -> Parser(input, Try(a, [Missing]c))
- Parser.one_of : List(Parser(input, a)) -> Parser(input, a)
+ Parser.one_of : List(Parser(input, a)) -> Parser(input, a) where input.len : input -> Num.U64
- Parser.one_or_more : Parser(input, a) -> Parser(input, List(a))
+ Parser.one_or_more : Parser(input, a) -> Parser(input, List(a)) where input.len : input -> Num.U64
- Parser.parse : Parser(input, a), input, (input -> Bool) -> Try(a, [ParsingFailure(Str), ParsingIncomplete(input)]c)
+ Parser.parse : Parser(input, a), input -> Try(a, [ParseError({ message : Str, offset : Num.U64 })]c) where input.len : input -> Num.U64
- Parser.parse_partial : Parser(input, a), input -> Parser.ParseResult(input, a)
+ Parser.run : Parser(input, a), input -> Parser.ParseResult(input, a) where input.len : input -> Num.U64
- Parser.sep_by : Parser(input, a), Parser(input, sep) -> Parser(input, List(a))
+ Parser.sep_by : Parser(input, a), Parser(input, sep) -> Parser(input, List(a)) where input.len : input -> Num.U64
- Parser.sep_by1 : Parser(input, a), Parser(input, sep) -> Parser(input, List(a))
+ Parser.sep_by_one_or_more : Parser(input, a), Parser(input, sep) -> Parser(input, List(a)) where input.len : input -> Num.U64
+ Parser.span : Parser(List(item), a) -> Parser(List(item), List(item))
---- String - MAJOR ----
Removed module
---- Utf8 - MINOR ----
Added module
---- Xml - MAJOR ----
- Xml : := { root : Xml.Node, xml_declaration : [Given(Xml.Declaration), Missing] }
+ Xml : := { declaration : Try(Xml.Declaration, [Missing]), root : Xml.Node }
- Xml.Declaration : (alias for { encoding : [Given(Xml.TextEncoding), Missing]#polarity, version : Xml.Version })
+ Xml.Declaration : (alias for { encoding : Try(Xml.TextEncoding, [Missing]#polarity), version : Xml.Version })
+ Xml.Error : (alias for { column : Num.U64, line : Num.U64, message : Str })
- Xml.Node : := [Element(Str, List({ name : Str, value : Str }), List(Xml.Node)), Text(Str)]
+ Xml.Node : := [Element({ attributes : List(Xml.Attribute), children : List(Xml.Node), name : Str }), Text(Str)]
+ Xml.Node.attribute : Xml.Node, Str -> Try(Str, [Missing]a)
+ Xml.Node.children_named : Xml.Node, Str -> List(Xml.Node)
+ Xml.Node.name : Xml.Node -> Try(Str, [NotAnElement]a)
+ Xml.Node.text : Xml.Node -> Str
+ Xml.Node.to_hash : a
- Xml.Version : (opaque type)
+ Xml.Version : (alias for { major : Num.U64, minor : Num.U64 })
- Xml.Version.is_eq : a
- Xml.Version.new : Num.U8 -> Xml.Version
+ Xml.parse_str : Str -> Try(Xml, [InvalidXml(Xml.Error)]a)
+ Xml.parser : Parser(List(Num.U8), Xml)
+ Xml.to_hash : a
- Xml.xml_parser : Parser(List(Num.U8), Xml)
---- Yaml - MAJOR ----
- Yaml : := [Bool(Bool), Float(Num.F64), Int(Num.I64), Mapping(List({ key : Str, value : Yaml })), Null, Sequence(List(Yaml)), String(Str)]
+ Yaml : := [Bool(Bool), Float(Num.F64), Int(Num.I64), Mapping(List({ key : Str, value : Yaml })), Null, Sequence(List(Yaml)), Text(Str)]
+ Yaml.Cursor : (opaque type)
+ Yaml.DecodeOptions : (alias for { keys : [CamelCase, KebabCase, SnakeCase]#polarity, unknown_keys : [Reject, Skip]#polarity })
- Yaml.Error : (alias for { column : Num.U64, ..{ line : Num.U64, message : Str } })
+ Yaml.Error : (alias for { column : Num.U64, line : Num.U64, message : Str })
+ Yaml.Format : (opaque type)
+ Yaml.Format.invalid_value : Yaml.Format, Yaml.Cursor -> [InvalidYaml({ column : Num.U64, line : Num.U64, message : Str })]a
+ Yaml.Format.parse_bool : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Bool }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_dec : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.Dec }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_dict_after_entry : Yaml.Format, Yaml.Cursor -> Try([Done(Yaml.Cursor)][Continue(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_dict_after_key : Yaml.Format, Yaml.Cursor -> Try(Yaml.Cursor, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_dict_next : Yaml.Format, Yaml.Cursor -> Try([Done(Yaml.Cursor)][Entry(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_dict_start : Yaml.Format, Yaml.Cursor -> Try([Counted({ len : Num.U64, rest : Yaml.Cursor }), Uncounted(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_f32 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.F32 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_f64 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.F64 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_i128 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I128 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_i16 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I16 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_i32 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I32 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_i64 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I64 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_i8 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I8 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_i16 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I16 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_i32 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I32 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_i64 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I64 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_i8 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.I8 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_str : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Str }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_u16 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U16 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_u32 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U32 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_u64 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U64 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_key_u8 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U8 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_list_after_item : Yaml.Format, Yaml.Cursor -> Try([Done(Yaml.Cursor)][Continue(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_list_next : Yaml.Format, Yaml.Cursor -> Try([Done(Yaml.Cursor)][Item(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_list_start : Yaml.Format, Yaml.Cursor -> Try([Counted({ len : Num.U64, rest : Yaml.Cursor }), Uncounted(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_null : Yaml.Format, Yaml.Cursor -> Try(Yaml.Cursor, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_record_after_field : Yaml.Format, Yaml.Cursor -> Try([Done(Yaml.Cursor)][Continue(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_record_field : Yaml.Format, Encoding.FieldName.FieldNames(_shape), Yaml.Cursor -> Try([Continue(Yaml.Cursor), Done(Yaml.Cursor), Field({ field : Encoding.FieldName(_shape), rest : Yaml.Cursor }), TryField({ name : Str, rest : Yaml.Cursor }), TryFieldCaseless({ name : Str, rest : Yaml.Cursor })]b, [InvalidYaml(Yaml.Error)]c)
+ Yaml.Format.parse_record_start : Yaml.Format, Yaml.Cursor -> Try([Counted({ len : Num.U64, rest : Yaml.Cursor }), Uncounted(Yaml.Cursor)]a, [InvalidYaml(Yaml.Error)]b)
+ Yaml.Format.parse_str : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Str }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_tag_union : Yaml.Format, Encoding.ParseTagUnionSpec(a), Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : a }, [InvalidYaml({ column : Num.U64, line : Num.U64, message : Str })]b)
+ Yaml.Format.parse_tuple_end : Yaml.Format, Yaml.Cursor, Num.U64 -> Try(Yaml.Cursor, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_tuple_next : Yaml.Format, Yaml.Cursor, Num.U64, Num.U64 -> Try(Yaml.Cursor, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_tuple_start : Yaml.Format, Yaml.Cursor, Num.U64 -> Try(Yaml.Cursor, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_u128 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U128 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_u16 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U16 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_u32 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U32 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_u64 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U64 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.parse_u8 : Yaml.Format, Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : Num.U8 }, [InvalidYaml(Yaml.Error)]a)
+ Yaml.Format.rename_field : Yaml.Format, Str -> Str
+ Yaml.Format.skip_record_field : Yaml.Format, Yaml.Cursor -> Try(Yaml.Cursor, [InvalidYaml(Yaml.Error)]a)
+ Yaml.as_bool : Yaml -> Try(Bool, [WrongType]a)
+ Yaml.as_i64 : Yaml -> Try(Num.I64, [WrongType]a)
+ Yaml.as_list : Yaml -> Try(List(Yaml), [WrongType]a)
+ Yaml.as_str : Yaml -> Try(Str, [WrongType]a)
+ Yaml.at : Yaml, Num.U64 -> Try(Yaml, [Missing]a)
+ Yaml.decode : Str -> Try(a, [InvalidYaml(Yaml.Error)]errs) where a.parser_for : Yaml.Format -> (Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : a }, [InvalidYaml(Yaml.Error)]errs))
+ Yaml.decoder : Yaml.DecodeOptions -> (Str -> Try(a, [InvalidYaml(Yaml.Error)]errs)) where a.parser_for : Yaml.Format -> (Yaml.Cursor -> Try({ rest : Yaml.Cursor, value : a }, [InvalidYaml(Yaml.Error)]errs))
+ Yaml.get : Yaml, Str -> Try(Yaml, [Missing]a)
+ Yaml.get_path : Yaml, List(Str) -> Try(Yaml, [Missing]a)
- Yaml.parse_str : Str -> Try(Yaml, [YamlError(Yaml.Error)]a)
+ Yaml.parse_str : Str -> Try(Yaml, [InvalidYaml(Yaml.Error)]a)
+ Yaml.parser : Parser(List(Num.U8), Yaml)
+ Yaml.to_hash : a
This is a MAJOR change.
1.2.0 -> 2.0.0
2.0.0 satisfies the required MAJOR bump.
Docs
- The documentation online, for the current release
- This release's own copy: the manual as a PDF, and as zipped static sites,
the manual and the API reference; unzip and openindex.html
Examples
The examples, with their parser dependency pinned to this release;
unzip, then follow the README.md inside.