@top Program { Query }

@precedence {
  attr @left
  unaryop
  mulop @left
  addop @left
  binop @left
  terop @left
  and @left
  or @left
}

@skip {
  space | LineComment
}

commaSep<content> { content ("," content)* }

kw<term> { @specialize[@name={term}]<Identifier, term> }

Query {
    TagIdentifier ( WhereClause | LimitClause | OrderClause | SelectClause | RenderClause )*
}

WhereClause { kw<"where"> Expression }
LimitClause { kw<"limit"> Expression }
OrderClause { Order commaSep<OrderBy> }
OrderBy { Expression OrderDirection? }
SelectClause { kw<"select"> commaSep<Select> }
RenderClause { kw<"render"> ( kw<"each"> | kw<"all"> )? PageRef }

Select { Identifier | Expression kw<"as"> Identifier }

OrderDirection {
 OrderKW
}

Value { Number | String | Bool | Regex | kw<"null"> | List }

Attribute {
  Expression !attr "." Identifier
}

Call {
    Identifier "(" commaSep<Expression> ")"
  | Identifier "(" ")"
}

TopLevelVal { "." }

ParenthesizedExpression { "(" Expression ")" }

LogicalExpression {
Expression !and kw<"and"> Expression
| Expression !or kw<"or"> Expression
}

Expression {
  Value
| Identifier
| GlobalIdentifier
| Attribute
| TopLevelVal
| ParenthesizedExpression
| LogicalExpression
| QueryExpression
| UnaryExpression
| BinExpression
| TernaryExpression
| Call
| PageRef
| Object
}

QueryExpression {
  "{" Query "}"
}

UnaryExpression {
    !unaryop NotKW Expression
  | !unaryop "!" Expression
  | !unaryop "-" Expression
}

BinExpression {
  Expression !binop "<" Expression
| Expression !binop "<=" Expression
| Expression !binop "=" Expression
| Expression !binop "!=" Expression
| Expression !binop ">=" Expression
| Expression !binop ">" Expression
| Expression !binop "=~" Expression
| Expression !binop "!=~" Expression
| Expression !binop InKW Expression

| Expression !mulop "*" Expression
| Expression !mulop "/" Expression
| Expression !mulop "%" Expression
| Expression !addop "+" Expression
| Expression !addop "-" Expression
}

TernaryExpression {
  Expression !terop "?" Expression !terop ":" Expression
}

List { "[" commaSep<Expression> "]" | "[" "]" }

KeyVal {
  String ":" Expression
}

Object { "{" commaSep<KeyVal> "}" | "{" "}" }

Bool {
  BooleanKW
}


@tokens {
  space { std.whitespace+ }

  LineComment { "#" ![\n]* }

  TagIdentifier { @asciiLetter (@asciiLetter | @digit | "-" | "_" | "/" )* }

  Identifier { @asciiLetter (@asciiLetter | @digit | "-" | "_")* |  "`" ![`]* "`" }

  GlobalIdentifier { "@" @asciiLetter (@asciiLetter | @digit | "-" | "_")* }

  String {
      "\"" ![\"]* "\""
    | "'" ![']* "'"
  }
  PageRef {
    "[[" ![\]]* "]]"
  }
  Order { "order by" }
  Regex { "/" ( ![/\\\n\r] | "\\" _ )+ "/" }

  Number { std.digit+ }

  BooleanKW { "true" | "false" }

  InKW { "in" }

  NotKW { "not" }

  OrderKW { "asc" | "desc" }

  @precedence { Order, BooleanKW, InKW, OrderKW, NotKW, Identifier, Number }
}