Predicator.Visitors.InstructionsVisitor (predicator v5.0.0)

Copy Markdown View Source

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

annotated()

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

visit(ast_node, opts \\ [])

@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 instructions
  • opts - Optional visitor options (currently unused)

Returns

List of instructions in the format [["operation", ...args]].

visit_with_positions(ast_node, opts \\ [])

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]], %{}}

visit_with_segment_positions(ast_node, opts \\ [])

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}]}}