Builds the raw-capture list Ichor.Actions expects ({:token,...}/
{:rule,...}/{:text,...}, see Ichor.Capture.raw_captures/0) from
one reduced production's own RHS entries -- shared by Grammar.LR (a
single linear stack) and Grammar.GLR (a graph-structured one): both
reduce the same kind of production, against the same kind of token
stream, needing the exact same per-position classification (see
Grammar.LRTable.Production's own moduledoc for what each capture
kind means), so this logic has exactly one place to live rather than
two copies drifting apart.
Every value it builds records the token range it matched
(Ichor.Capture.at/3), for Ichor.Actions to turn into its span. A
helper's captures, spliced in, were recorded when the helper was
reduced and keep their ranges.
Built as a list, in production.captures' own RHS-position order,
rather than a map -- a name's position (first occurrence, for one
captured more than once) is real information Ichor.Actions relies
on (Ichor.Capture.position/0) to evaluate sibling captures in true
source order, which a plain map's own iteration order can't be
trusted to reconstruct (it isn't guaranteed to match insertion order,
and has been observed to differ across Erlang/OTP versions).
Summary
Types
One popped RHS position: value is the shifted %Token{} (a
terminal position) or the already-built raw-captures list of a
reduced nonterminal; start_pos/end_pos are the token-stream span
it covers, needed for a :text-kind capture regardless of which RHS
position it sits at (not just the whole production's own span).
Functions
Builds the raw-captures list for production, given its popped RHS entries in order.
The first/second/third+ list-promotion rule every raw-capture builder
in Ichor uses (Grammar.Native.Runtime.Parser.merge_captures/2,
mirrored here for the LR/GLR side): a name captured more than once
naturally becomes a list without knowing the eventual count in
advance. A new name is appended at the end, landing at its
first-occurrence RHS position relative to whatever's already there
(build/3 calls this in RHS order); a repeat name updates its
existing entry in place, not moving it.
Types
@type entry() :: {value :: Grammar.VM.Token.t() | Ichor.Capture.raw_captures(), start_pos :: non_neg_integer(), end_pos :: non_neg_integer()}
One popped RHS position: value is the shifted %Token{} (a
terminal position) or the already-built raw-captures list of a
reduced nonterminal; start_pos/end_pos are the token-stream span
it covers, needed for a :text-kind capture regardless of which RHS
position it sits at (not just the whole production's own span).
Functions
@spec build(Grammar.LRTable.Production.t(), [entry()], tuple()) :: Ichor.Capture.raw_captures()
Builds the raw-captures list for production, given its popped RHS entries in order.
@spec merge(Ichor.Capture.raw_captures(), atom(), term()) :: Ichor.Capture.raw_captures()
The first/second/third+ list-promotion rule every raw-capture builder
in Ichor uses (Grammar.Native.Runtime.Parser.merge_captures/2,
mirrored here for the LR/GLR side): a name captured more than once
naturally becomes a list without knowing the eventual count in
advance. A new name is appended at the end, landing at its
first-occurrence RHS position relative to whatever's already there
(build/3 calls this in RHS order); a repeat name updates its
existing entry in place, not moving it.