Visitor that converts AST nodes to stack machine instructions.
This visitor implements post-order traversal to generate instruction lists that can be executed by the stack-based evaluator. Instructions are generated in the correct order for stack-based evaluation.
Source positions
Internally the traversal pairs every instruction with the source position of
the AST node that emitted it. visit/2 discards those positions and returns
the plain instruction list; visit_with_positions/2 returns both. The
instruction list is identical either way - positions never enter the
instruction format itself, so cross-language interchange and stored compiled
artifacts are unaffected (ADR-0001). The paired value is whatever the node
carried in its trailing slot, so an AST parsed with spans: true yields a
span table rather than a position table.
Both entry points require a node with a trailing slot. A caller hand-building
an AST supplies nil there, which produces no entry in the position table.
Examples
iex> ast = {:literal, 42, nil}
iex> Predicator.Visitors.InstructionsVisitor.visit(ast, [])
[["lit", 42]]
iex> ast = {:identifier, "score", nil}
iex> Predicator.Visitors.InstructionsVisitor.visit(ast, [])
[["load", "score"]]
iex> ast = {:comparison, :gt, {:identifier, "score", nil}, {:literal, 85, nil}, nil}
iex> Predicator.Visitors.InstructionsVisitor.visit(ast, [])
[["load", "score"], ["lit", 85], ["compare", "GT"]]
iex> ast = {:logical_and, {:literal, true, nil}, {:literal, false, nil}, nil}
iex> Predicator.Visitors.InstructionsVisitor.visit(ast, [])
[["lit", true], ["jump_if_falsy_or_pop", 2], ["lit", false]]
iex> ast = {:function_call, "len", [{:identifier, "name", nil}], nil}
iex> Predicator.Visitors.InstructionsVisitor.visit(ast, [])
[["load", "name"], ["call", "len", 1]]
iex> ast = {:if, {:literal, true, nil}, {:block, [], nil}, nil, nil}
iex> Predicator.Visitors.InstructionsVisitor.visit(ast, [])
[["lit", true], ["pop_jump_if_falsy", 1]]
Summary
Types
One instruction paired with the source position of the node that emitted it.
Functions
Visits an AST node and returns stack machine instructions.
Returns the instruction list and a side table mapping each instruction's 0-based index to the source position of the AST node that emitted it.
Returns the instruction list, the position table visit_with_positions/2
returns, and a per-store segment-position table.
Types
@type annotated() :: {[binary() | term()], Predicator.Types.position() | Predicator.Types.span() | nil} | {[binary() | term()], Predicator.Types.position() | Predicator.Types.span() | nil, [Predicator.Types.position() | Predicator.Types.span() | nil]}
One instruction paired with the source position of the node that emitted it.
Every clause but one produces the two-element form. A ["store", n]
instruction produces the three-element form instead, whose third element is
location_segment_annotations/1's list - one annotation per lhs segment,
root-first. Nothing else in this module ever emits or reads a third element.
Functions
@spec visit( Predicator.Parser.visitable(), keyword() ) :: [[binary() | term()]]
Visits an AST node and returns stack machine instructions.
Uses post-order traversal to ensure operands are pushed onto the stack before operators are applied.
Parameters
ast_node- The AST node to convert to instructionsopts- Optional visitor options (currently unused)
Returns
List of instructions in the format [["operation", ...args]].
@spec visit_with_positions( Predicator.Parser.visitable(), keyword() ) :: {[[binary() | term()]], Predicator.Types.position_table() | Predicator.Types.span_table()}
Returns the instruction list and a side table mapping each instruction's 0-based index to the source position of the AST node that emitted it.
Nodes carrying a nil position contribute no entry, so a position-free AST
yields an empty table. The instruction list is identical to visit/2's.
Examples
iex> ast = {:comparison, :gt, {:identifier, "score", {1, 1}}, {:literal, 85, {1, 9}}, {1, 7}}
iex> Predicator.Visitors.InstructionsVisitor.visit_with_positions(ast)
{[["load", "score"], ["lit", 85], ["compare", "GT"]],
%{0 => {1, 1}, 1 => {1, 9}, 2 => {1, 7}}}
iex> Predicator.Visitors.InstructionsVisitor.visit_with_positions({:literal, 42, nil})
{[["lit", 42]], %{}}
@spec visit_with_segment_positions( Predicator.Parser.visitable(), keyword() ) :: {[[binary() | term()]], Predicator.Types.position_table() | Predicator.Types.span_table(), Predicator.Types.segment_position_table()}
Returns the instruction list, the position table visit_with_positions/2
returns, and a per-store segment-position table.
The segment table maps each ["store", n] instruction's 0-based index to
the list location_segment_annotations/1 built for its lhs chain - one
annotation per location segment, root-first, whatever n counts as depth
(location_depth/1). Every other instruction contributes no entry. The
instruction list and the position table are identical to what
visit_with_positions/2 returns for the same AST - this function differs
only in returning the third table alongside them.
Examples
iex> {:ok, program} = Predicator.parse_program("a.b = 1", spans: false)
iex> Predicator.Visitors.InstructionsVisitor.visit_with_segment_positions(program)
{[["lit", "a"], ["lit", "b"], ["lit", 1], ["store", 2]],
%{0 => {1, 1}, 1 => {1, 3}, 2 => {1, 7}, 3 => {1, 1}},
%{3 => [{1, 1}, {1, 3}]}}