All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.5.0] - 2026-10-20
Added
- Every capture knows where it matched:
%Ichor.Capture{}has aspan,{line, column, length}, and a default%Ichor.Node{}the span of its rule -- until now itsspanwas always nil. Nil when the parse recorded no positions: a node handed toevaluate_node/3, or a parser generated before this release. Ichor.Capture.at/3records the token range a raw capture matched, andplain/1takes those records off again. A parser wraps every capture value withat/3;Ichor.Actionsunwraps them before an action sees anything, so.nodeis as it always was.Grammar.Source.Locator: where a token range of a parse is in its input, as a span. Built once per parse.Ichor.Actions.evaluate/6takes options::locator, the parse's locator, and:range, the token range its root matched.
Fixed
- An action's error says where. An error a
handle_rule/3orhandle_token/3returns with no line is placed where that rule or token matched, and shown with its line and a caret; nothing else about it changes. It used to come back with no position at all, so a domain error raised in an action -- a refused map key, a schema violation -- could not be shown where it was. The innermost place wins: an error that already has a line keeps it.
Changed
Grammar.LRTable.Captures.build/3records the token range of every value it builds (Ichor.Capture.at/3). An LR or GLR parser generated byichor0.5 or earlier builds through it, so itsparse/2returns captures with the records in them; regenerate it withichor0.6, whoseparse/2takes them off (plain/1).
0.4.0 - 2026-10-19
Added
Ichor.Errorhas adatafield: whatever a stage hands through with its error -- most often an action failing with its own domain error, which its caller unwraps. Nil unless something sets it;format/1ignores it.- The lossless tree,
Ichor.Syntax: every byte of an input, as tokens -- trivia and punctuation included, with byte offsets, lines and columns -- under every grammar rule that matched them (Ichor.Syntax.Node,Ichor.Syntax.Token).tree/3weaves a token stream into a parse's rule records;text/1is a node's input, and the root's is the whole input, byte for byte;tokens/1its leaves. Trivia never sits at a node's edge: it belongs to the deepest node with something significant on both sides. The runtime half ofichor's lossless mode. Ichor.Syntax.Record: how a lossless parser records its rules, inside its ordinary captures, so backtracking and merging handle them as they handle every capture.Grammar.LR.Lossless: the LR loop for a lossless parse, table-driven so the generated fast parser's state functions are left as they are.Grammar.GLR.Runtime.run/7: takes the function that builds each reduction's captures;run/6builds them as before.
Fixed
- An LR or GLR rule repeated three times or more is a flat list.
Splicing a repetition's helper into a capture the production already
held nested the helper's list as one value:
doc := entry+with three entries gave[a, [b, c]], whichIchor.Actionscould not evaluate. The splice now appends the list's elements, as the PEG backends do. Grammar.Source.placed/2keeps every field of an error it shows. It rebuilt the error field by field, so a field it did not name was lost; it now fills in only the snippet.
0.3.0 - 2026-10-18
Changed
- Licensed under the Apache License 2.0 (
Apache-2.0); it was MIT.
Added
Grammar.Source.trimmed/2: a token stream without the@skiptokens before its first other token and after its last, which every engine and backend now applies before matching the root, so trivia around an input means the same to all of them.Grammar.Source.position/3andplaced/2: where a position in a token stream is -- its token, or past the last one the end of the input -- and an error that says nowhere placed at the end of the input. No input at all is line 1, column 1.- Every error shows where:
Grammar.Source.placed/2also gives an error with a position and no snippet the line it is on and a caret, so an LR or GLR error renders like a PEG one -- it used to say where and show nothing. Grammar.LRTable.unexpected_error/3: "unexpected end of input" with the end of the input's line and column.unexpected_error/2stays for parsers generated before it.- Docs for the
mainbranch are now published to GitHub Pages (.github/workflows/docs.yml) on every push, at joetjen.github.io/ichor_runtime. Released versions continue to be documented on hexdocs.pm;mix.exs'sdocs()sets acanonicalURL pointing there so the two copies don't compete as duplicate content.
0.2.0 - 2026-08-03
Fixed
Ichor.Actions.eval_all/2could evaluate sibling captures in the wrong order (and, for an Actions module implementing custom special-form logic that assumes a particular evaluation order, threadcontextincorrectly) because it relied on a plain Elixir map's own iteration order, which Elixir/Erlang make no guarantee about -- observed to actually differ between Erlang/OTP versions for the exact same code and input.
Changed
- Breaking: raw capture data (
Ichor.Capture.node_t/0's:rulevariant,Ichor.Actions.evaluate/5'sraw_capturesargument,Grammar.LRTable.Captures.build/3's return value, andGrammar.Native.Runtime.Parser.merge_captures/2's accumulator) is now an ordered list of{name, value}pairs (Ichor.Capture.raw_captures/0) instead of a plain map, preserving true first-occurrence RHS/source order end to end -- the fix for the ordering bug above. A grammar's generated Actions module, or any hand-written code that pattern-matches or constructsraw_captures/node_t()values directly (e.g. for macro-expansion viaIchor.Actions.evaluate_node/3), needs updating from%{name: value}to[{name, value}]/[name: value]. The already- evaluatedIchor.Actions.captures/0map handed tohandle_rule/3callbacks is unchanged (still a plain map, keyed for ergonomic access). Ichor.Capturegained apositionfield (defaults to0) recording a capture's index among its siblings;eval_all/2now sorts by it before folding, instead of trustingcaptures' own map order.
0.1.0 - 2026-07-29
Added
- First release, extracted from
ichor's own monorepo (packages/ichor_runtime, viagit subtree split-- this repository's history starts with the commits that originally added that directory) into its own independently-published-and-maintained project. ~24 modules: everything amix ichor.gen-generated parser (oruse Ichor-compiled module) actually calls at runtime, plus three self-contained toolkit modules useful to an engine built on top of one:- Capture dispatch:
Ichor.Actions,Ichor.Capture,Ichor.Node,Ichor.Error. - The compiled Tokenizer/Parser runtime:
Grammar.Native.Runtime.Parser/Tokenizer,Grammar.VM.Token,Grammar.Source,Grammar.Lexer. - The LR/GLR runtime:
Grammar.LRTable,Grammar.LRTable.Production/.Captures,Grammar.LR.Stack,Grammar.GLR.GSS/.Runtime. @native(...)callback behaviours:Ichor.CustomLexeme,Ichor.CustomRule.- Standalone toolkit:
Ichor.Toolkit.Pratt(precedence-climbing expression parsing),Ichor.Toolkit.TermWalk(genericfold/rewriterecursion),Ichor.Backtrack(+.Bindings/.Term/.Tree, a lazy-search-plus-unification substrate for logic-language evaluation models),Ichor.Toolkit.Result(reduce_ok/map_ok).
- Capture dispatch: