A compiled program and its source-location table, as one value.
Returned by Predicator.compile_with_positions/1 and
Predicator.compile_with_spans/1, and accepted directly by
Predicator.evaluate/3, which threads the table itself - so the table cannot
be dropped between compilation and evaluation (ADR-0009).
What to store
This struct is an in-memory Elixir value and is not a wire format. A
consumer that persists a compiled program stores compiled.instructions - a
bare JSON array, byte-identical to what Predicator.compile/1 emits for the
same source. Do not serialize the struct: positions and segment_positions
both hold offsets into the source string the program was compiled from, and
they are meaningless to anything that does not also hold that string.
segment_positions is a second Elixir-side derived table, present for the
same reason and with the same storage advice as positions - it is empty
for a program compiling no assignment. An expression compiles no store, so
compile_with_positions/1 and compile_with_spans/1 always return an empty
one; compile_program_with_positions/1 populates it whenever the program
contains an assignment.
A program loaded back from storage as a bare list evaluates fine and reports
position: nil on runtime errors, which is correct - the source is gone.
A consumer that wants positions back after a round trip should persist the
source, not the table, and recompile with compile_with_positions/1 (or
compile_with_spans/1) on load - recompiling the same source is
deterministic and yields an identical table every time. The table itself is
a derived fact, the same reasoning ADR-0009 already applied to reject an
isa_version field: a cached copy of a derived fact can disagree with the
thing it claims to describe. Nothing checks that a positions table
actually came from the instructions list it is attached to, so a table
compiled from one source and attached to another source's instructions
produces no error - just a confidently wrong position, which is worse than
the honest position: nil an unpaired instruction list reports.
Examples
iex> compiled = Predicator.Compiled.new(
...> [["load", "score"], ["lit", 85], ["compare", "GT"]],
...> %{0 => {1, 1}, 1 => {1, 9}, 2 => {1, 7}}
...> )
iex> compiled.instructions
[["load", "score"], ["lit", 85], ["compare", "GT"]]
iex> compiled.positions
%{0 => {1, 1}, 1 => {1, 9}, 2 => {1, 7}}Recompiling the same source is deterministic, which is what makes "persist the source, recompile on load" a sound way to get positions back:
iex> {:ok, first} = Predicator.compile_with_positions("score > 85")
iex> {:ok, second} = Predicator.compile_with_positions("score > 85")
iex> first == second
true
Summary
Functions
Pairs an instruction list with a source-location table and, optionally, a segment-position table.
Types
@type t() :: %Predicator.Compiled{ instructions: Predicator.Types.instruction_list(), positions: Predicator.Types.position_table() | Predicator.Types.span_table(), segment_positions: Predicator.Types.segment_position_table() }
A compiled program paired with the source-location table for its instructions.
positions is a Predicator.Types.position_table/0 under
Predicator.compile_with_positions/1 and a
Predicator.Types.span_table/0 under Predicator.compile_with_spans/1.
One field, not two: nothing below the façade distinguishes them -
Predicator.Evaluator reads either without knowing which, and
Predicator.Errors.put_position/2 discriminates a nil, a point, and a span
at the point of use.
The struct deliberately carries no ISA version: it is computable from the
instruction list by Predicator.Instructions.required_isa/1, and a stored
copy of a derived fact can disagree with the list it claims to describe
(ADR-0003, ADR-0009).
segment_positions is a new field (px-ids), added rather than folded
into positions: ADR-0009 treats adding a field to this struct as additive
and reshaping positions's meaning as not, so the per-store segment table
gets its own field rather than changing what positions holds.
Functions
@spec new( Predicator.Types.instruction_list(), Predicator.Types.position_table() | Predicator.Types.span_table(), Predicator.Types.segment_position_table() ) :: t()
Pairs an instruction list with a source-location table and, optionally, a segment-position table.
For a caller who stored a bare instruction list and kept its tables
separately - Predicator.compile_with_positions/1,
Predicator.compile_with_spans/1, and Predicator.compile_program_with_positions/1
build the struct themselves.
Examples
iex> compiled = Predicator.Compiled.new([["lit", 42]], %{0 => {1, 1}})
iex> compiled.positions
%{0 => {1, 1}}
iex> compiled.segment_positions
%{}
iex> Predicator.Compiled.new([["lit", 42]]).positions
%{}
iex> compiled = Predicator.Compiled.new(
...> [["lit", "a"], ["lit", "b"], ["lit", 1], ["store", 2]],
...> %{0 => {1, 1}, 1 => {1, 2}, 2 => {1, 7}, 3 => {1, 1}},
...> %{3 => [{1, 1}, {1, 2}]}
...> )
iex> compiled.segment_positions
%{3 => [{1, 1}, {1, 2}]}