# `Expression.AST`

Type definitions for the Expression abstract syntax tree.

The AST is produced by `Expression.Parser` and consumed by `Expression.Eval`.
It is also used by consumers for transpilation (e.g., to Elasticsearch queries)
and static analysis (e.g., function validation). This module formalizes the
AST as a public API.

## Top-Level Structure

`Expression.Parser.parse/1` returns a keyword list of `:text` and `:expression`
entries:

    [text: "hello ", expression: [atom: "world"]]

`Expression.parse_expression!/1` returns the inner expression AST without
the `:text`/`:expression` wrappers.

## Node Types

### Literals
`{:literal, value}` — a constant value (integer, float, string, boolean, Date, DateTime, Time).

    {:literal, 42}
    {:literal, "hello"}
    {:literal, true}
    {:literal, ~D[2022-01-31]}

### Variables
`{:atom, name}` — a variable reference. Names are always lowercased strings.

    {:atom, "contact"}
    {:atom, "name"}

### Attribute Access (dot notation)
`{:attribute, [parent, child]}` — property access via `.` notation.
Each element is itself an AST node.

    # @contact.name
    {:attribute, [{:atom, "contact"}, {:atom, "name"}]}

    # @contact.details.age
    {:attribute, [{:attribute, [{:atom, "contact"}, {:atom, "details"}]}, {:atom, "age"}]}

### Key Access (bracket notation)
`{:key, [subject, key_expr]}` — index access via `[expr]` notation.

    # @items[0]
    {:key, [{:atom, "items"}, {:literal, 0}]}

### Function Calls
`{:function, [name: name, args: args]}` — a function invocation.
Name is a lowercased string. Args is a list of AST nodes.

    # SUM(1, 2)
    {:function, [name: "sum", args: [literal: 1, literal: 2]]}

    # upper(contact.name)
    {:function, [name: "upper", args: [{:attribute, [{:atom, "contact"}, {:atom, "name"}]}]]}

Infix `and`/`or`/`not` also produce function nodes:

    # a and b
    {:function, [name: "and", args: [atom: "a", atom: "b"]]}

    # not a
    {:function, [name: "not", args: [{:atom, "a"}]]}

### Operators
`{operator, [left, right]}` — binary arithmetic and comparison operators.

    # 1 + 2
    {:+, [literal: 1, literal: 2]}

    # age > 18
    {:>, [{:atom, "age"}, {:literal, 18}]}

Supported operators: `:+`, `:-`, `:*`, `:/`, `:^`, `:&`,
`:>`, `:>=`, `:<`, `:<=`, `:==`, `:!=`

### Lists
`{:list, [args: elements]}` — a list literal. Empty list: `{:list, []}`.

    # [1, 2, 3]
    {:list, [args: [literal: 1, literal: 2, literal: 3]]}

### Ranges
`{:range, [first, last]}` or `{:range, [first, last, step]}` — a numeric range.

    # 1..10
    {:range, [1, 10]}

    # 1..10//2
    {:range, [1, 10, 2]}

### Lambdas
`{:lambda, [args: body]}` — an anonymous function using `&(...)` syntax.

    # &(&1 + 1)
    {:lambda, [args: {:+, [{:capture, 1}, {:literal, 1}]}]}

### Captures
`{:capture, index}` — a lambda parameter reference (`&1`, `&2`, etc.).

    {:capture, 1}
    {:capture, 2}

## Operator Precedence (lowest to highest)

1. `or` (infix logical)
2. `and` (infix logical)
3. `not` (prefix logical)
4. `==`, `!=`, `>`, `>=`, `<`, `<=` (comparison)
5. `+`, `-`, `&` (addition, concatenation)
6. `*`, `/` (multiplication)
7. `^` (exponentiation)

# `ast_node`

```elixir
@type ast_node() ::
  literal()
  | variable()
  | attribute()
  | key()
  | function_call()
  | binary_op()
  | list_node()
  | range()
  | lambda()
  | capture()
```

Any AST node

# `attribute`

```elixir
@type attribute() :: {:attribute, [ast_node()]}
```

Dot-notation property access

# `binary_op`

```elixir
@type binary_op() ::
  {:+, [ast_node()]}
  | {:-, [ast_node()]}
  | {:*, [ast_node()]}
  | {:/, [ast_node()]}
  | {:^, [ast_node()]}
  | {:&amp;, [ast_node()]}
  | {:&gt;, [ast_node()]}
  | {:&gt;=, [ast_node()]}
  | {:&lt;, [ast_node()]}
  | {:&lt;=, [ast_node()]}
  | {:==, [ast_node()]}
  | {:!=, [ast_node()]}
```

Binary operator expression

# `block`

```elixir
@type block() :: [ast_node()]
```

A parsed expression block — the output of `Expression.parse_expression!/1`

# `capture`

```elixir
@type capture() :: {:capture, pos_integer()}
```

A lambda parameter reference

# `expression`

```elixir
@type expression() :: {:expression, [ast_node()]}
```

An expression segment in a parsed template

# `function_call`

```elixir
@type function_call() :: {:function, name: String.t(), args: [ast_node()]}
```

A function call

# `key`

```elixir
@type key() :: {:key, [ast_node()]}
```

Bracket-notation index access

# `lambda`

```elixir
@type lambda() :: {:lambda, [{:args, ast_node()}]}
```

A lambda function

# `list_node`

```elixir
@type list_node() :: {:list, list() | [{:args, [ast_node()]}]}
```

A list literal

# `literal`

```elixir
@type literal() ::
  {:literal,
   number() | String.t() | boolean() | Date.t() | DateTime.t() | Time.t()}
```

A literal value

# `range`

```elixir
@type range() :: {:range, [integer()]}
```

A numeric range

# `template`

```elixir
@type template() :: [text() | expression()]
```

A parsed template — the output of `Expression.parse!/1`

# `text`

```elixir
@type text() :: {:text, String.t()}
```

A text segment in a parsed template

# `variable`

```elixir
@type variable() :: {:atom, String.t()}
```

A variable reference (always lowercased)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
