Skip to content

3.0.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 04:56
Immutable release. Only release title and notes can be modified.
c542066

roc-parser 3.0.0

3.0.0 is the first final release since 2.0.0. It adds JsonValue, a lossless
JSON reader and writer; compile-time parsing for every format; RocSyntax for
Roc source; AsciiDoc parsing and rendering with include selection that
matches Asciidoctor exactly; and source-preserving YAML tokens. The 2.1.0
release candidates previewed part of this work. 2.1.0 itself was never
published.

Use this release

Add the package to your app's header:

parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/3.0.0/6xsRdRqenJrAb4wG7HaST4Xh349vD5sHAJDsKTK6E3dr.tar.zst",

The release also carries the examples, pinned to this bundle, the manual as a
PDF and as a static site with the API reference, and an SBOM.

Upgrade impact

  • Requires a current Roc nightly: nightly-2026-10-21-5349569 or later.
    roc-parser now tracks the latest Roc nightly. The "Compilers" section below names the
    nightly this release was built and tested with.
  • From 2.0.0: existing runtime parsing functions keep their signatures and
    acceptance rules. The new checked conversions reject enabled lint checks by
    default, and each check can be disabled through defaulted props. The
    unicode dependency is now
    4.3.0.
  • Why a major version: roc bump cannot compare this release with 2.0.0,
    which no longer compiles on current Roc nightlies, or with the 2.1.0
    candidates (roc#12506), so the
    version cannot be checked mechanically. AsciiDoc rendering also changed
    since the release candidates, and the manual counts any change in a public
    function's result as a major change.
  • JSON module name: the module is JsonValue. It is not called Json because Roc
    auto-imports a builtin Json, and its error tag is InvalidJsonValue so
    that it does not collide with the builtin's InvalidJson(Str).
  • From the 2.1.0 release candidates: AsciiDoc.empty_item and
    AsciiDoc.without_internal_caches are no longer public, and
    include:: expansion now selects exactly the lines Asciidoctor 2.0.23
    selects, so rendered output changes for these inputs:
    • indent= removes leading tabs as well as spaces, so tab-indented code
      included with indent=0 loses its outer tab. Each tab counts as one
      character. A line of only spaces in a file that isn't AsciiDoc counts toward
      the shared indent, and indent=N leaves empty lines empty.
    • A tag re-opened before its end nests, so it needs a second end.
    • The innermost open tag decides, so tags=x;!y selects x inside y.
    • With tags=*, an end that crosses another tag is ignored.
    • A tag marker must start a word, and its [] must end the line or come
      before a space. So "tag::x[]" in a string literal is no longer a marker.
    • An empty lines=, tag= or indent= counts as present. An empty
      lines= or tag= selects the whole file and ignores the other
      selectors, and an empty indent= dedents.
    • tag=a;b names one tag, a;b. Use tags=a;b for two tags.
    • An empty file includes no lines, so it no longer splits a paragraph.
    • Lines from an included file that isn't AsciiDoc (any name not ending in
      .adoc, .asciidoc, .asc, .ad or .txt) keep their trailing
      whitespace, as in Asciidoctor. Listings of program output keep spaces at line
      ends. In a paragraph, list item or admonition the spaces stay in the text, a
      line of only spaces no longer ends a paragraph, and a delimiter or block
      attribute line with trailing whitespace is ordinary text. Directives such as
      ifdef:: and include:: in such a file are no longer processed.
    • The document's own lines lose trailing whitespace before directives and
      delimiters are recognized, so a ---- delimiter or an ifdef::x[]
      directive followed by spaces now works.
    • tabsize is now honoured. In listing, source, literal and verse blocks and
      paragraphs, tabs expand to the next multiple of the block's tabsize or,
      without one, the document's, counting characters. Zero, negative or
      non-numeric values expand nothing. In a document that sets tabsize, include
      indent= expands tabs first; a tabsize= on the directive is ignored, as
      in Asciidoctor.
    • A verbatim block's indent follows Asciidoctor: only the block's own
      attribute counts, indent=abc dedents and a negative width keeps
      indentation, empty lines stay empty, and passthrough, stem and table content is
      no longer re-indented. A [source], [listing], [literal] or [verse]
      paragraph whose first line is indented keeps its style. Indent widths and tab
      sizes above 1024 count as 1024.
    • A backtick followed by an apostrophe renders as a right single quote, as in
      Asciidoctor, so a possessive written after inline code renders the same way
      Asciidoctor renders it. A backslash before the pair keeps both characters.

New features

JsonValue

JsonValue reads JSON whose shape a record type cannot describe, such as
software bills of materials, JSON Schema files and configuration with
comments, and writes it back out. Use the builtin Json to decode JSON into
records.

  • Lossless values. A JsonValue is Null, Bool, Number, String,
    Array or Object. Numbers keep their exact text and objects keep their
    member order. get, at, get_path, keys and as_str, as_bool,
    as_list and as_object walk a value; as_i64, as_u64 and as_dec
    convert a number.
  • Strict by default. parse_str accepts only RFC 8259 JSON, and also
    rejects repeated keys and nesting deeper than 128 levels. Errors are
    InvalidJsonValue({ line, column, message }).
  • Options. parse_with takes a JsonValue.Options record with defaulted
    fields:
    • limits: max_depth, max_items, max_string_bytes and
      max_number_digits;
    • relaxations, each off by default: comments and trailing_commas
      (JSONC), bom, non_finite (NaN and Infinity),
      control_characters, and lone_surrogates: Replace;
    • checks: duplicates (Reject, CollapseIdentical, KeepFirst or
      KeepLast), top_level, and interoperable_numbers (I-JSON).
  • Writer. to_str writes compact JSON and to_str_with takes indent
    and sort_keys. The layout matches Python's
    json.dumps(ensure_ascii=False) byte for byte, with number text kept as
    written. Values JSON cannot express are refused with InvalidNumber,
    DuplicateKey or TooDeep.
  • Compile time. A string literal annotated JsonValue is checked during
    compilation, and JsonValue.validate checks an imported file with the same
    options as parse_with.
  • Composition. parser and parser_with are Parser(Utf8.Bytes, JsonValue)
    values for use with the combinators.
  • Evidence. scripts/review_json.py compares the parser and writer with
    Python's json module on 397 hand-written cases, the 318 JSONTestSuite
    cases and five pinned SPDX, CycloneDX, Syft and Trivy documents: all 720
    pass, with no known failures. Three fuzz targets (json-raw,
    json-options and json-roundtrip) run in CI with the other targets, and
    review_json.py campaign adds a differential robustness campaign.
    Benchmarks compare it with serde_json, Go's encoding/json and Python's
    json.

AsciiDoc include selection

AsciiDoc.select_include(source, attributes) selects lines from an include
source exactly as include:: expansion does, so tools that expand includes
themselves, such as roc-docs, no longer reimplement the rules. It reads
lines=, tag=, tags= (with *, ** and ! negation) and
indent=, and returns an AsciiDoc.IncludeSelection with the selected
text, the one-based source lines and a list of AsciiDoc.IncludeProblem
values. Selection is Asciidoctor's, as lenient as it is. MissingTag,
UnclosedTag and UnmatchedEnd are exactly Asciidoctor's warnings, while
InvalidLines and EmptySelection flag selections that Asciidoctor makes
without a warning, so a strict caller can reject them. The source is read as
a file that isn't AsciiDoc, as code listings are.
AsciiDoc.select_include_with(source, attributes, { tabsize }), with
AsciiDoc.IncludeContext, selects lines for a document that sets tabsize.
scripts/asciidoc/include-selection-fixtures.json compares the selections and
problems with Asciidoctor 2.0.23. See "Reusing include selection" in
Reading AsciiDoc.

Compile-time parsing

  • Compile-time quotations for YAML, XML, CSV, JSON, Markdown, AsciiDoc, HTTP,
    and Roc source, plus typed YAML and CSV decoding.
  • Direct nominal document types for CSV, AsciiDoc, and RocSyntax, with
    from_quote methods; Markdown.Document and HTTP request/response quotation
    targets cover their distinct document shapes.
  • Configurable validate and validate_as conversions, simple located errors,
    and Literal.require for demanded top-level constants from imported text.
  • Utf8.parse_quote for custom combinator-based quotation types.
  • An enduring compile-time parsing guide, runnable examples with adjacent
    assets, and compiler rejection and cache-invalidation tests in CI.

See Parse and validate during compilation
and Reading JSON.

RocSyntax

RocSyntax parses Roc source into source-backed tokens, a tolerant indexed syntax tree and located diagnostics. It is meant for highlighters and source-analysis tools.

  • The tree represents expressions, patterns, type declarations, constraints and every header form.
  • Parsing has explicit nesting and token limits. It does not do binding or type analysis.
  • Malformed input gets partial structure, and its tokens can still be highlighted.
  • tokenize_str, tokenize_bytes and highlight_category serve lexical highlighting without building a tree.

Legacy Rust compiler syntax is outside its scope. See the manual's "Parsing Roc source" chapter.

On the development machine, every input in the reference performance comparison parses within 1.5 times the time of the compiler's own Zig parser. The syntax parsed matches compiler commit 0d4b0ab0. That grammar is still changing, so expect follow-up releases as the language evolves.

AsciiDoc

AsciiDoc adds block parsing, preprocessing on virtual sources, and embedded HTML rendering. It is tested against a pinned Asciidoctor on authored fixtures, independently generated models and reproducible mutations. Compatibility with Asciidoctor is not complete; see the manual's AsciiDoc chapter for what is covered.

YAML source highlighting

Yaml.tokenize_str and Yaml.tokenize_bytes cover the original source with tokens, keeping its exact bytes. They include comments, whitespace and malformed snippets. They also report mapping-key roles, YAML 1.2 scalar categories and located diagnostics. Yaml.highlight_category maps tokens to theme categories that HTML and PDF renderers can share.

Semantic YAML decoding is unchanged.

Documentation and tooling

  • New manual chapters for Roc syntax, AsciiDoc and YAML source highlighting, each with tested examples.
  • scripts/check_roc_syntax.py runs the Roc syntax feedback loop. It includes a persistent compiler reference worker, independent generated syntax, raw and structured fuzzing, an upstream snapshot corpus, and runtime, allocation and scaling checks.
  • AsciiDoc and YAML source fuzz targets run in the property quality workflow.
  • A new "Read and write JSON" chapter, and JSON conformance, fuzzing and
    benchmark coverage.
  • CI follows the roc-automation model: one check job runs every pull request
    check, releases are published through roc-automation's publish-release,
    and a daily job tests the latest release's examples against the latest Roc
    nightly.

Release candidates

2.1.0-rc.1, rc.2 and rc.3 previewed RocSyntax, AsciiDoc and YAML source
highlighting. Since rc.3 the package moved to the latest Roc nightly and
unicode 4.3.0, gained JsonValue, compile-time parsing and include selection,
and finished the Roc syntax performance work.

Compilers

Roc compiler version nightly-2026-10-21-5349569