Predicator.Types (predicator v7.0.0)

Copy Markdown View Source

Core type definitions for the Predicator library.

This module defines all the fundamental types used throughout the Predicator system for instructions, evaluation contexts, and results.

Summary

Types

The evaluation context containing variable bindings.

A duration representing a time span.

The internal state of the stack machine evaluator.

A single instruction in the stack machine.

A list of instructions that form a complete program.

The internal result of evaluating a predicate from Evaluator functions.

Location path for assignment expressions.

Result type for location expression evaluation.

A source position: 1-based line and column of the token that defines an AST node.

Maps a 0-based instruction index to the source position of the AST node that emitted it. Produced by Predicator.Compiler.to_instructions_with_positions/2 and carried as compiled.positions on the Predicator.Compiled.t/0 that Predicator.compile_with_positions/1 returns; never part of the instruction list itself, so interchange and stored compiled artifacts are unaffected.

The result of evaluating a predicate from public API functions.

SCXML-compatible error result for enhanced error handling.

SCXML-compatible result type for value expressions.

Maps a ["store", n] instruction's 0-based index to one source annotation per location segment in the assignment's lhs chain, root-first.

A source span: the start and end of the source text an AST node covers.

Maps a 0-based instruction index to the span of the AST node that emitted it.

A single value that can be used in predicates.

Functions

Checks if two values have matching types for operations.

Checks if a value is undefined.

Types

context()

@type context() :: %{required(binary() | atom()) => value()}

The evaluation context containing variable bindings.

Context maps variable names (strings or atoms) to their values. Both string and atom keys are supported for flexibility.

Examples

%{"score" => 85, "name" => "Alice"}
%{score: 85, name: "Alice"}

duration()

@type duration() :: %{
  years: non_neg_integer(),
  months: non_neg_integer(),
  weeks: non_neg_integer(),
  days: non_neg_integer(),
  hours: non_neg_integer(),
  minutes: non_neg_integer(),
  seconds: non_neg_integer(),
  milliseconds: non_neg_integer()
}

A duration representing a time span.

Duration is represented as a map with fields for different time units:

  • years - number of years (default: 0)
  • months - number of months (default: 0)
  • weeks - number of weeks (default: 0)
  • days - number of days (default: 0)
  • hours - number of hours (default: 0)
  • minutes - number of minutes (default: 0)
  • seconds - number of seconds (default: 0)

Examples

%Duration{days: 3, hours: 8}  # 3 days 8 hours
%Duration{weeks: 2}           # 2 weeks
%Duration{minutes: 30}        # 30 minutes

evaluator_state()

@type evaluator_state() :: %{
  instructions: instruction_list(),
  instruction_pointer: non_neg_integer(),
  stack: [value()],
  context: context(),
  halted: boolean()
}

The internal state of the stack machine evaluator.

Contains:

  • instructions - list of instructions to execute
  • instruction_pointer - current position in instruction list
  • stack - evaluation stack (top element is head of list)
  • context - variable bindings
  • halted - whether execution has stopped

instruction()

@type instruction() :: [binary() | value()]

A single instruction in the stack machine.

Instructions are lists where the first element is the operation name and remaining elements are arguments. The full opcode set - every instruction the evaluator accepts, its operands, stack effect, and error semantics - is specified in docs/isa.md.

No instruction carries a source position. Positions travel in a separate position_table/0, produced alongside the instruction list by Predicator.Compiler.to_instructions_with_positions/2 and available as compiled.positions on the Predicator.Compiled.t/0 that Predicator.compile_with_positions/1 returns, so the instruction format stays exactly what the Ruby and JavaScript siblings interchange (ADR-0001). That side table holds span/0 values instead when the AST was parsed with spans: true; the instruction format is unaffected either way.

Examples

["lit", 42]           # Push literal 42 onto stack
["load", "score"]     # Load variable 'score' from context
["compare", "GT"]     # Pop two values, compare with >, push result

instruction_list()

@type instruction_list() :: [instruction()]

A list of instructions that form a complete program.

Instructions are executed in order by the stack machine.

internal_result()

@type internal_result() :: value() | {:error, term()}

The internal result of evaluating a predicate from Evaluator functions.

Returns:

  • value() - the final evaluation result value
  • {:error, term()} - evaluation error with details

location_path()

@type location_path() :: [binary() | non_neg_integer()]

Location path for assignment expressions.

Represents the path to an assignable location in nested data structures:

  • Simple property: ["user", "name"]
  • Array index: ["items", 0, "price"]
  • Dynamic property: ["user", "settings", "theme"] (for user.settings["theme"])

location_result()

@type location_result() :: {:ok, location_path()} | scxml_error()

Result type for location expression evaluation.

Returns:

  • {:ok, location_path()} - valid assignment path
  • scxml_error() - error if location is not assignable

position()

@type position() :: {line :: pos_integer(), column :: pos_integer()}

A source position: 1-based line and column of the token that defines an AST node.

position_table()

@type position_table() :: %{required(non_neg_integer()) => position()}

Maps a 0-based instruction index to the source position of the AST node that emitted it. Produced by Predicator.Compiler.to_instructions_with_positions/2 and carried as compiled.positions on the Predicator.Compiled.t/0 that Predicator.compile_with_positions/1 returns; never part of the instruction list itself, so interchange and stored compiled artifacts are unaffected.

result()

@type result() :: {:ok, value()} | {:error, term()}

The result of evaluating a predicate from public API functions.

Returns:

  • {:ok, value()} - successful evaluation with result value
  • {:error, term()} - evaluation error with details

scxml_error()

@type scxml_error() ::
  {:error, :undefined_variable, %{variable: binary()}}
  | {:error, :type_mismatch, %{expected: atom(), got: atom()}}
  | {:error, :invalid_location, %{expression: binary()}}
  | {:error, :evaluation_error, %{reason: binary()}}
  | {:error, :parse_error,
     %{message: binary(), line: integer(), column: integer()}}

SCXML-compatible error result for enhanced error handling.

Returns structured error information for SCXML datamodel compatibility:

  • {:error, :undefined_variable, %{variable: binary()}} - Variable not found in context
  • {:error, :type_mismatch, %{expected: atom(), got: atom()}} - Type mismatch in operation
  • {:error, :invalid_location, %{expression: binary()}} - Invalid assignment target
  • {:error, :evaluation_error, %{reason: binary()}} - General evaluation error
  • {:error, :parse_error, %{message: binary(), line: integer(), column: integer()}} - Parse error

scxml_result()

@type scxml_result() :: {:ok, value()} | scxml_error()

SCXML-compatible result type for value expressions.

Returns:

  • {:ok, value()} - successful evaluation with result value
  • scxml_error() - structured error with SCXML-compatible details

segment_position_table()

@type segment_position_table() :: %{
  required(non_neg_integer()) => [position() | span() | nil]
}

Maps a ["store", n] instruction's 0-based index to one source annotation per location segment in the assignment's lhs chain, root-first.

A segment's annotation is nil when the node that produced it carried none; otherwise it is a position/0 or a span/0, matching whichever mode position_table/0 or span_table/0 is in for the same compile. Produced by Predicator.Compiler.to_instructions_with_segment_positions/2 and carried as compiled.segment_positions on the Predicator.Compiled.t/0 that returns it. Like position_table/0 this is never part of the instruction list, so interchange and stored compiled artifacts are unaffected.

span()

@type span() :: {start :: position(), end_exclusive :: position()}

A source span: the start and end of the source text an AST node covers.

The end is exclusive - it names the position one past the last character - so on a single line end_column - start_column is the span's length, matching LSP ranges. The identifier score at line 1 column 1 spans {{1, 1}, {1, 6}}.

A span composes two position/0 values; a point position and a span answer different questions, and both are available. See Predicator.Parser.parse/2's :spans option.

span_table()

@type span_table() :: %{required(non_neg_integer()) => span()}

Maps a 0-based instruction index to the span of the AST node that emitted it.

The span-mode counterpart of position_table/0, produced by Predicator.Compiler.to_instructions_with_positions/2 from an AST parsed with spans: true, and carried as compiled.positions on the Predicator.Compiled.t/0 that Predicator.compile_with_spans/1 returns. Like the position table it is never part of the instruction list, so interchange and stored compiled artifacts are unaffected.

value()

@type value() ::
  boolean()
  | integer()
  | float()
  | binary()
  | list()
  | Date.t()
  | DateTime.t()
  | duration()
  | :undefined
  | nil

A single value that can be used in predicates.

Values can be:

  • boolean() - true/false values
  • integer() - integer numeric values
  • float() - floating-point numeric values
  • binary() - string values
  • list() - lists of values
  • Date.t() - date values
  • DateTime.t() - datetime values
  • duration() - duration values for time spans
  • :undefined - represents an absence: an unbound variable or a value that could not be produced; see Predicator.Undefined for the canonical definition of the sentinel
  • nil - the null value: present, but with no content. Distinct from :undefined, which is an absence. nil === :undefined is false.

Functions

types_match?(a, b)

@spec types_match?(value(), value()) :: boolean()

Checks if two values have matching types for operations.

Two values have matching types if they are both:

  • integers
  • booleans
  • binaries (strings)
  • lists
  • maps (objects)
  • dates
  • datetimes

Examples

iex> Predicator.Types.types_match?(1, 2)
true

iex> Predicator.Types.types_match?("hello", "world")
true

iex> Predicator.Types.types_match?(%{a: 1}, %{b: 2})
true

iex> Predicator.Types.types_match?(1, "hello")
false

undefined?(value)

@spec undefined?(value()) :: boolean()

Checks if a value is undefined.

Delegates to Predicator.Undefined.undefined?/1 - see that module for the canonical definition of the :undefined sentinel.

Examples

iex> Predicator.Types.undefined?(:undefined)
true

iex> Predicator.Types.undefined?(42)
false