# AsterQL Grammar

This is the implementation-driven grammar for AsterQL 0.5.0. It describes what
the scanner, parser, and evaluator currently accept, not a future language
wish list.

## Lexical Grammar

AsterQL ignores whitespace and `//` line comments.

```ebnf
Identifier      ::= [A-Za-z_] [A-Za-z0-9_]*
Parameter       ::= "$" Identifier
AtIdentifier    ::= "@" Identifier

Number          ::= Digit+ ("." Digit+)? Exponent?
Exponent        ::= ("e" | "E") ("+" | "-")? Digit+

String          ::= DoubleString | SingleString
DoubleString    ::= '"' StringChar* '"'
SingleString    ::= "'" StringChar* "'"
StringChar      ::= any non-terminating character
                  | "\\" ('"' | "'" | "/" | "\\" | "b" | "f" | "n" | "r" | "t")
                  | "\\u" Hex Hex Hex Hex

Boolean         ::= "true" | "false"
Null            ::= "null"
EqualitySymbol  ::= "=" | "==" | "==="
```

`true`, `false`, and `null` are literals. The word `in` is an infix operator
when it appears in operator position. The word `fold` starts a fold expression
only when it is immediately parsed as `fold {...}`; otherwise it is an ordinary
identifier.

Negative numbers are parsed as unary `-` applied to a positive number.

## Program

```ebnf
Program         ::= ConstDeclaration* Expression EOF
ConstDeclaration
                ::= "const" Identifier "=" Expression ";"
Expression      ::= BinaryExpression
```

## Expressions

The parser is a small precedence-climbing parser around prefix, postfix, and
binary expressions.

```ebnf
BinaryExpression
                ::= PrefixExpression (BinaryOperator PrefixExpression)*

PrefixExpression
                ::= Literal
                  | Identifier
                  | AtIdentifier
                  | Parameter
                  | Scope
                  | Function
                  | UnaryExpression
                  | Array
                  | Object
                  | Fold
                  | Parenthesized

Literal         ::= Number | String | Boolean | Null
Scope           ::= "*" | "$" | "@" | "^"
Function        ::= "->" Expression
UnaryExpression ::= ("!" | "+" | "-") Expression
Parenthesized   ::= "(" Expression ")"
```

### Operator Precedence

Higher numbers bind tighter. All binary operators are currently left
associative.

| Level | Operators                          |
| ----: | ---------------------------------- |
|    10 | prefix `!`, prefix `+`, prefix `-` |
|     9 | `*`, `/`, `%`                      |
|     8 | `+`, `-`                           |
|     7 | `<`, `<=`, `>`, `>=`               |
|     6 | `=`, `==`, `===`, `!=`, `in`, `~`  |
|     5 | `&&`                               |
|     4 | <code>&#124;&#124;</code>          |
|     3 | `??`                               |
|     2 | `->`                               |
|     1 | <code>&#124;</code>                |

`|` is syntactically just another binary operator, but the language treats it
as the intentional collection-stage composition point. The left value becomes
the right expression's current value.

### Pipe Stages

The right side of a `|` is a pipe stage. Most stages are ordinary expressions —
a call such as `sort(...)`, `group(...)`, or `filter(...)`, a `fold {...}`, or a
bare traversal. The bracket and brace forms are special-cased:

```ebnf
PipeStage       ::= "[" Expression "]"   // filter stage
                  | "{" Members? "}"     // projection stage
                  | Expression
```

- `| [condition]` is a filter stage: it keeps the current items whose condition
  is truthy. It mirrors postfix `[condition]` but reads as an explicit step.
- `| {...}` projects the current pipe value with the same member grammar as a
  postfix projection.

A pipe filter stage requires a condition and does not accept slice syntax. Use
`slice($, start, end)` to slice inside a pipeline.

## Postfix Expressions

Postfix expressions chain after any prefix expression.

```ebnf
PostfixExpression
                ::= PrefixExpression Postfix*

Postfix         ::= Call
                  | AttributeAccess
                  | WildcardAccess
                  | RecursiveAccess
                  | BracketAccess
                  | Projection
                  | Exists

Call            ::= "(" Arguments? ")"
Arguments       ::= Expression ("," Expression)* ","?

AttributeAccess ::= "." PropertyName
WildcardAccess  ::= ".*"
RecursiveAccess ::= ".." PropertyName | "..*"
Exists          ::= "?"

PropertyName    ::= Identifier | String
```

## Bracket Access

The meaning of bracket access is decided after parsing the bracket expression.

```ebnf
BracketAccess   ::= "[]"
                  | "[" ":" Expression? "]"
                  | "[" Expression ":" Expression? "]"
                  | "[" Expression "]"
```

Semantics:

- `[]` is explicit fan-out.
- `[:end]`, `[start:end]`, and `[start:]` are slices.
- `["field"]` is attribute access.
- `[0]` and `[-1]` are index access. Index syntax is limited to a numeric
  literal or unary negative numeric literal.
- Any other `[expr]` is a filter.

Slices are end-exclusive. Negative indexes and negative slice bounds buffer the
input because they must know the collection length.

## Arrays And Objects

```ebnf
Array           ::= "[" Elements? "]"
Elements        ::= Expression ("," Expression)* ","?

Object          ::= "{" Members? "}"
Projection      ::= "{" Members? "}"
Fold            ::= "fold" "{" Members? "}"

Members         ::= Member ("," Member)* ","?
Member          ::= Spread | Property
Spread          ::= "..." Expression
Property        ::= ObjectKey (":" Expression)?
ObjectKey       ::= Identifier | String
```

Identifier object keys may be shorthand:

```asterql
{id, title}
```

String object keys must provide an explicit value:

```asterql
{"post-title": title}
```

Projection syntax and object literal syntax share the same member grammar. A
projection is parsed when an object-shaped member list appears as a postfix
expression after a base value.

## Scope Semantics

```asterql
*     // root input value
$     // current value
@     // legacy current-value spelling
^     // parent current value
$id   // host parameter named "id"
```

Bare identifiers read attributes from the current value:

```asterql
title
```

Use `$.name` for explicit current-value attribute access. Legacy `@name` reads
the same attribute. In joins, `$` remains pinned to the left-hand join value
while the right-hand expression traverses candidate values.

Missing fields evaluate to `null`, and existence checks use `?`.

## Built-In Calls

The default built-ins are:

```ebnf
BuiltinCall     ::= Identifier "(" Arguments? ")"
```

Recognized names:

- `len(value?)`
- `sum(expr?)`
- `avg(expr?)`
- `min(expr?)`
- `max(expr?)`
- `any(expr?)`
- `all(expr?)`
- `unique(expr?)`
- `keys(value?)`
- `type(value?)`
- `when(condition, then, else?)`
- `filter(expr?)`
- `select(expr?)`
- `map(expr?)`
- `slice(value, start?, end?)`
- `sort(expr, ...)`
- `sort_by(expr, ...)`
- `group(expr?)`
- `length(value?)`
- `reverse(value?)`
- `add(value?)`

`sort`, `filter`, and `group` are intended for pipe stages. `slice` also works
as a value transform on arrays, sequences, and strings, with the same bounds
semantics as postfix slices. `fold {...}` is a dedicated expression rather than a
normal call.

`sum(total)` and `avg(total)` are reducer calls: the first argument is evaluated
once per item in the current collection. Hosts may provide custom scalar
functions through `EvaluationOptions.functions` and custom collection reducers
through `EvaluationOptions.reducers`. Custom functions receive materialized
argument values plus a context object containing the current value, root value,
and parameters. Custom reducers receive an iterable of materialized values plus
evaluated extra arguments and parameters.

`when(condition, then, else?)` is the expression-level conditional. It evaluates
and returns `then` when the condition is truthy (the same truthiness as filters)
and `else` otherwise, returning `null` when the untaken branch is omitted. Only
the taken branch is evaluated.

`=`, `==`, and `===` parse to the same equality operator. There is no JavaScript
coercion distinction between the spellings.

## Unsupported Syntax

These forms are intentionally not part of AsterQL 0.5.0:

- `=>` conditional object members.
- Range syntax such as `1..10`.
- Namespaces such as `math::sum`.
- Reassignment, mutation, imports, async functions, or hidden resolver calls.
