# `Linx.NFT.Parser`
[🔗](https://github.com/oshlabs/linx/blob/v0.2.0/lib/linx/nft/parser.ex#L1)

Recursive-descent parser over a token stream produced by
`Linx.NFT.Tokenizer`. Builds a small internal AST that
`Linx.NFT.Compiler` later walks and translates into calls on the
`Linx.Netfilter.Ruleset` validator-setter surface (the same
surface the pipeline DSL uses — no parallel validation layer).

Mirrors the shape of `Phoenix.LiveView.TagEngine.Parser`: a
function per non-terminal, a token list as the threaded state,
consume helpers that raise `Linx.NFT.ParseError` on mismatch
with the token's `{file, line, column}` and source snippet.

## AST shape

Top-level items (the result of `parse/1`):

    {:table, family, name, body, meta}
    {:include, path, meta}
    {:define, name, value, meta}

Inside a table `body`:

    {:chain, name, opts, stmts, meta}
    {:set, name, opts, meta}
    {:map, name, opts, meta}
    {:vmap, name, opts, meta}
    {:object, kind, name, opts, meta}
    {:flowtable, name, opts, meta}

Inside a chain `stmts`:

    {:rule, exprs, rule_opts, meta}

Inside a rule's `exprs` (one node per source-level statement,
whether a match clause, a verdict, or an action):

    {:match, lhs, op, rhs, meta}
    {:verdict, kind, meta}            # :accept, :drop, {:jump, "chain"}, ...
    {:counter, opts, meta}
    {:log, opts, meta}
    {:limit, rate, opts, meta}
    {:nat, kind, target, opts, meta}  # kind: :dnat | :snat | :masquerade | :redirect
    {:meta_set, field, value, meta}   # `meta mark set 0xdead`
    {:reject, opts, meta}
    {:queue, opts, meta}

LHS (left-hand side of a match):

    {:payload, header, field, meta}   # `tcp dport`, `ip saddr`
    {:meta, field, meta}              # `meta iif`
    {:ct, field, meta}                # `ct state`
    {:set_ref, name, meta}            # bare `@blocklist` as predicate
    {:not, inner_lhs, meta}           # `not @blocklist`

RHS values (the value-position grammar):

    {:integer, n, meta}               | {:string, s, meta}
    {:address, kind, raw, meta}       # :ipv4 / :ipv6 / :mac / :cidr_v4 / :cidr_v6
    {:identifier, name, meta}         # bare identifier (e.g. `established`)
    {:set_inline, [vals], meta}       # `{ 22, 80, 443 }`
    {:set_ref, name, meta}            # `@blocklist`
    {:range, lo, hi, meta}            # `22-25`
    {:list, [vals], meta}             # `22, 80, 443` (no braces)
    {:elixir_expr, raw, meta}         # `#{...}` interpolation
    {:wildcard, meta}                 # `*` (e.g. `iifname "eth*"`)

## Scope notes

The parser covers the structural shape and the slice of the
grammar needed for the canonical `~NFT` examples in
`docs/netfilter/EXAMPLES.md`. The set of recognised statement
/ lhs / rhs shapes will grow as the compiler and long-tail
extensions add callers (see `docs/netfilter/DESIGN.md`). The
architectural commitments — recursive descent, raise-on-mismatch,
file:line:column on every AST node — are finalised here.

# `ast`

```elixir
@type ast() :: tuple()
```

# `parse`

```elixir
@spec parse(
  [tuple()],
  keyword()
) :: {:ok, [ast()]} | {:error, Linx.NFT.ParseError.t()}
```

Parses a token list into a list of top-level AST items.

## Options

  * `:file` — source filename for error messages
    (default `"nofile"`).
  * `:source` — original source binary for snippet rendering
    (default `""`).

Returns `{:ok, ast_items}` or `{:error, %Linx.NFT.ParseError{}}`.

---

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