@top Program { Query }

@precedence {
  mulop @left
  addop @left
  binop @left
  and @left
  or @left
}

@skip {
  space
}

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 {
  LVal "." Identifier
}

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

LVal {
  Identifier
| Attribute
}

ParenthesizedExpression { "(" Expression ")" }

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

Expression {
  Value
| LVal
| ParenthesizedExpression
| LogicalExpression 
| BinExpression
| Call
}

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
}

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


Bool {
  BooleanKW
}


@tokens {
  space { std.whitespace+ }

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

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

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

  Number { std.digit+ }

  BooleanKW { "true" | "false" }

  InKW { "in" }

  OrderKW { "asc" | "desc" }

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