# `A2UIAgent.Express.Syntax`
[🔗](https://github.com/a2ui-elixir/a2ui/blob/v0.1.1/umbrella/apps/a2ui_agent/lib/a2ui_agent/express/syntax.ex#L1)

Reads Express source into statements, following the grammar
`specification/inference_formats/express/Express.g4`.

    iex> A2UIAgent.Express.Syntax.parse(~s|root = Text("Hi", variant="h1")|)
    {:ok, [{:assign, "root", {:call, "Text", ["Hi"], [{"variant", "h1"}]}}], []}

A statement is `{:assign, target, value}`, where the target is a
variable name or a data path (`"$/a/b"`), or `{:expr, value}`. A value
is a JSON literal (a string, number, boolean or `nil`), a list, or one
of:

  * `{:map, pairs}` — a map literal, keys in source order;
  * `{:path, path}` — a data binding, `$/a/b` as `"/a/b"`;
  * `{:check, name, args}` — a check rule such as `?regex("^a")`;
  * `{:call, name, args, kwargs}` — a call, keyword arguments as
    pairs in source order;
  * `{:var, name}` — a variable reference;
  * `:skipped` — the `_` placeholder.

A keyword argument or map key given twice keeps its first place and
its last value.

`parse/1` returns the statements before the first syntax error with
the errors it found, each `{line, column, message, kind}` where `kind`
is `:lexer` for a character no token starts with and `:parser`
otherwise. Lines count from 1 and columns from 0.

# `statement`
*since 0.1.0* 

```elixir
@type statement() :: {:assign, String.t(), value()} | {:expr, value()}
```

An Express statement.

# `syntax_error`
*since 0.1.0* 

```elixir
@type syntax_error() ::
  {pos_integer(), non_neg_integer(), String.t(), :lexer | :parser}
```

A syntax error: line, column, message and the stage that found it.

# `value`
*since 0.1.0* 

```elixir
@type value() ::
  String.t()
  | number()
  | boolean()
  | nil
  | [value()]
  | {:map, [{String.t(), value()}]}
  | {:path, String.t()}
  | {:check, String.t(), [value()]}
  | {:call, String.t(), [value()], [{String.t(), value()}]}
  | {:var, String.t()}
  | :skipped
```

An Express value.

# `identifier?`
*since 0.1.0* 

```elixir
@spec identifier?(term()) :: boolean()
```

Whether `name` can be written as a bare Express identifier: it matches
`[a-zA-Z_][a-zA-Z0-9_]*` and is not `true`, `false`, `null` or `_`.

    iex> A2UIAgent.Express.Syntax.identifier?("content_grid")
    true
    iex> A2UIAgent.Express.Syntax.identifier?("content-grid")
    false

# `parse`
*since 0.1.0* 

```elixir
@spec parse(String.t()) :: {:ok, [statement()], [syntax_error()]}
```

Parses `source`. Returns `{:ok, statements, errors}`: when `errors` is
not empty, `statements` holds the statements that start on a line
before the first error.

# `unescape`
*since 0.1.0* 

```elixir
@spec unescape(String.t()) :: String.t()
```

Resolves the escapes a standard or triple-quoted string supports:
`\n`, `\r`, `\t`, `\\` and `\"`. Any other backslash stays as
written.

    iex> A2UIAgent.Express.Syntax.unescape(~S(a\nb\d))
    "a\nb\\d"

---

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