Linx.NFT.Parser (Linx v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

Types

ast()

@type ast() :: tuple()

Functions

parse(tokens, opts \\ [])

@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{}}.