Pentiment.Elixir (pentiment v0.1.5)
Helpers for extracting spans from Elixir AST metadata.
This module provides convenience functions for working with Elixir's compile-time metadata, making it easy to integrate Pentiment with macros and DSLs.
Usage in Macros
defmacro my_macro(expr) do
span = Pentiment.Elixir.span_from_ast(expr)
source = Pentiment.Elixir.source_from_env(__CALLER__)
if invalid?(expr) do
report = Pentiment.Report.error("Invalid expression")
|> Pentiment.Report.with_source(__CALLER__.file)
|> Pentiment.Report.with_label(Pentiment.Label.primary(span, "here"))
raise CompileError, description: Pentiment.format(report, source)
end
# ... normal macro expansion
endMetadata Keys
Elixir AST metadata typically includes:
:line- Line number (1-indexed, always present):column- Column number (1-indexed, often present):end_line- End line for multi-line nodes (optional):end_column- End column (optional):file- File path (sometimes present)
Summary
Functions
Extracts file path from a Macro.Env struct.
Extracts the leftmost span from two AST nodes.
Extracts line number from a Macro.Env struct.
Creates a Source from a Macro.Env struct.
Creates a span for a literal value at a known position.
Extracts a span from an Elixir AST node.
Extracts a span from a Macro.Env struct.
Extracts a span from Elixir AST metadata.
Returns the display length of a value as it would appear in source code.
Functions
@spec file_from_env(Macro.Env.t()) :: String.t() | nil
Extracts file path from a Macro.Env struct.
Examples
iex> Pentiment.Elixir.file_from_env(__CALLER__)
"lib/my_app.ex"
@spec leftmost_span(term(), term()) :: Pentiment.Span.Position.t() | nil
Extracts the leftmost span from two AST nodes.
Useful for binary operators where the first operand typically has better position information.
Examples
iex> left = quote do: x
iex> right = quote do: y
iex> Pentiment.Elixir.leftmost_span(left, right)
# Returns span from `left` if available, otherwise from `right`
@spec line_from_env(Macro.Env.t()) :: pos_integer() | nil
Extracts line number from a Macro.Env struct.
Examples
iex> Pentiment.Elixir.line_from_env(__CALLER__)
42
@spec source_from_env(Macro.Env.t()) :: Pentiment.Source.t() | nil
Creates a Source from a Macro.Env struct.
Reads the source file from the environment's file path.
Examples
defmacro my_macro(expr) do
source = Pentiment.Elixir.source_from_env(__CALLER__)
# ...
end
@spec span_for_value(term(), pos_integer(), pos_integer()) :: Pentiment.Span.Position.t()
Creates a span for a literal value at a known position.
Computes the display length for common Elixir literals:
- Atoms: includes the leading colon (e.g.,
:foo= 4 chars) - Integers: digit count
- Strings: includes quotes (e.g.,
"hi"= 4 chars) - Variables/identifiers: string length
Examples
iex> Pentiment.Elixir.span_for_value(:foo, 5, 10)
%Pentiment.Span.Position{start_line: 5, start_column: 10, end_line: 5, end_column: 14}
iex> Pentiment.Elixir.span_for_value(12345, 1, 1)
%Pentiment.Span.Position{start_line: 1, start_column: 1, end_line: 1, end_column: 6}
@spec span_from_ast(Macro.t() | term()) :: Pentiment.Span.Position.t() | nil
Extracts a span from an Elixir AST node.
Works with:
- Raw Elixir AST tuples:
{name, meta, args} - Structs with a
:metafield
When possible, computes the end column from the AST structure:
- Variables: uses the variable name length
- Function calls with
:closingmetadata: uses the closing paren/bracket position - Maps, binaries, anonymous functions: uses closing metadata
- Aliases: uses
:lastmetadata for multi-part aliases - Block expressions (case, cond, etc.): uses
:endmetadata - Otherwise: falls back to metadata only
Examples
iex> ast = quote do: x + y
iex> Pentiment.Elixir.span_from_ast(ast)
%Pentiment.Span.Position{start_line: ..., start_column: ...}
iex> Pentiment.Elixir.span_from_ast(:not_ast)
nil
@spec span_from_env(Macro.Env.t()) :: Pentiment.Span.Position.t() | nil
Extracts a span from a Macro.Env struct.
Note: Macro.Env only provides line information, not column.
Examples
iex> Pentiment.Elixir.span_from_env(__CALLER__)
%Pentiment.Span.Position{start_line: 42, start_column: 1}
@spec span_from_meta(keyword()) :: Pentiment.Span.Position.t() | nil
Extracts a span from Elixir AST metadata.
Accepts a keyword list of metadata (as found in AST nodes).
Examples
iex> Pentiment.Elixir.span_from_meta([line: 10, column: 5])
%Pentiment.Span.Position{start_line: 10, start_column: 5}
iex> Pentiment.Elixir.span_from_meta([line: 10, column: 5, end_line: 10, end_column: 15])
%Pentiment.Span.Position{start_line: 10, start_column: 5, end_line: 10, end_column: 15}
iex> Pentiment.Elixir.span_from_meta([])
nil
@spec value_display_length(term()) :: pos_integer()
Returns the display length of a value as it would appear in source code.
Examples
iex> Pentiment.Elixir.value_display_length(:foo)
4 # `:foo`
iex> Pentiment.Elixir.value_display_length(12345)
5
iex> Pentiment.Elixir.value_display_length("hello")
7 # `"hello"`