///|
/// The abstract syntax tree.
///
/// It is CPython's, node for node and field for field, following
/// `Parser/Python.asdl` for every construct PurePy needs to NAME -- to accept,
/// or to reject with the right message. Being CPython's is the point: the
/// reference checker walks CPython nodes, so a checker written against this
/// tree can be put beside `statements.py` and read line for line, and the AST
/// oracle can compare our dump against `ast.dump` over the whole suite.
///
/// It is typed rather than stringly: operators, expression contexts and
/// pattern kinds are enums, not strings.
///
/// Five deliberate departures from `Python.asdl`, each earning its keep:
///
///   * `MatchSequence` carries the bracket it was written with. The spec
///     distinguishes a list pattern from a tuple pattern and CPython does not,
///     so the reference recovers the bracket from the source text; this port
///     records it where it is known, in the parser.
///   * Async is a FLAG on `FunctionDef`, `For` and `With`, not three more node
///     kinds. The sieve's message is `async prohibited` either way.
///   * `Constant` is a typed enum, not a Python object. `Complex` and `Bytes`
///     are arms so that the sieve can say "complex literals prohibited".
///   * An f-string is `JoinedStr` with its text unparsed. The sieve rejects
///     f-strings as not yet supported (#55); when that changes, the parts are
///     parsed here and nothing else moves.
///   * Positions are a `Span` of two `Pos`, and the column counts CODE POINTS.
///     CPython's `col_offset` counts UTF-8 bytes; `Source::byte_col` converts,
///     and only where a message in the reference's format needs it.
///
/// Deliberately absent: `type_comment`, `type_ignores`, `type_params`,
/// `TemplateStr` and `Interpolation`. PurePy is Python 3.12 and PEP 695 syntax
/// is a syntax error here.
pub(all) struct Module {
  body : Array[Stmt]
  span : @basic.Span
} derive(Eq, Debug)

///|
pub(all) enum Stmt {
  FunctionDef(
    name~ : String,
    args~ : Arguments,
    body~ : Array[Stmt],
    decorators~ : Array[Expr],
    returns~ : Expr?,
    is_async~ : Bool,
    span~ : @basic.Span
  )
  ClassDef(
    name~ : String,
    bases~ : Array[Expr],
    keywords~ : Array[Keyword],
    body~ : Array[Stmt],
    decorators~ : Array[Expr],
    span~ : @basic.Span
  )
  Return(value~ : Expr?, span~ : @basic.Span)
  Delete(targets~ : Array[Expr], span~ : @basic.Span)
  Assign(targets~ : Array[Expr], value~ : Expr, span~ : @basic.Span)
  AugAssign(target~ : Expr, op~ : Operator, value~ : Expr, span~ : @basic.Span)
  AnnAssign(
    target~ : Expr,
    annotation~ : Expr,
    value~ : Expr?,
    simple~ : Bool,
    span~ : @basic.Span
  )
  For(
    target~ : Expr,
    iter~ : Expr,
    body~ : Array[Stmt],
    or_else~ : Array[Stmt],
    is_async~ : Bool,
    span~ : @basic.Span
  )
  While(
    cond~ : Expr,
    body~ : Array[Stmt],
    or_else~ : Array[Stmt],
    span~ : @basic.Span
  )
  If(
    cond~ : Expr,
    body~ : Array[Stmt],
    or_else~ : Array[Stmt],
    span~ : @basic.Span
  )
  With(
    items~ : Array[WithItem],
    body~ : Array[Stmt],
    is_async~ : Bool,
    span~ : @basic.Span
  )
  Match(subject~ : Expr, cases~ : Array[MatchCase], span~ : @basic.Span)
  Raise(exc~ : Expr?, cause~ : Expr?, span~ : @basic.Span)
  Try(
    body~ : Array[Stmt],
    handlers~ : Array[ExceptHandler],
    or_else~ : Array[Stmt],
    finalbody~ : Array[Stmt],
    is_star~ : Bool,
    span~ : @basic.Span
  )
  Assert(cond~ : Expr, msg~ : Expr?, span~ : @basic.Span)
  Import(names~ : Array[Alias], span~ : @basic.Span)
  ImportFrom(
    module_name~ : String?,
    names~ : Array[Alias],
    level~ : Int,
    span~ : @basic.Span
  )
  Global(names~ : Array[String], span~ : @basic.Span)
  Nonlocal(names~ : Array[String], span~ : @basic.Span)
  ExprStmt(value~ : Expr, span~ : @basic.Span)
  Pass(span~ : @basic.Span)
  Break(span~ : @basic.Span)
  Continue(span~ : @basic.Span)
} derive(Eq, Debug)

///|
pub(all) enum Expr {
  BoolOp(op~ : BoolOp, values~ : Array[Expr], span~ : @basic.Span)
  NamedExpr(target~ : Expr, value~ : Expr, span~ : @basic.Span)
  BinOp(left~ : Expr, op~ : Operator, right~ : Expr, span~ : @basic.Span)
  UnaryOp(op~ : UnaryOp, operand~ : Expr, span~ : @basic.Span)
  Lambda(args~ : Arguments, body~ : Expr, span~ : @basic.Span)
  IfExp(cond~ : Expr, body~ : Expr, or_else~ : Expr, span~ : @basic.Span)
  /// A `None` key is `**e` unpacking, as in the ASDL.
  Dict(keys~ : Array[Expr?], values~ : Array[Expr], span~ : @basic.Span)
  Set(elts~ : Array[Expr], span~ : @basic.Span)
  ListComp(elt~ : Expr, generators~ : Array[Comprehension], span~ : @basic.Span)
  SetComp(elt~ : Expr, generators~ : Array[Comprehension], span~ : @basic.Span)
  DictComp(
    key~ : Expr,
    value~ : Expr,
    generators~ : Array[Comprehension],
    span~ : @basic.Span
  )
  GeneratorExp(
    elt~ : Expr,
    generators~ : Array[Comprehension],
    span~ : @basic.Span
  )
  Await(value~ : Expr, span~ : @basic.Span)
  Yield(value~ : Expr?, span~ : @basic.Span)
  YieldFrom(value~ : Expr, span~ : @basic.Span)
  Compare(
    left~ : Expr,
    ops~ : Array[CmpOp],
    comparators~ : Array[Expr],
    span~ : @basic.Span
  )
  Call(
    func~ : Expr,
    args~ : Array[Expr],
    keywords~ : Array[Keyword],
    span~ : @basic.Span
  )
  /// An f-string, its parts unparsed (#55). `raw` is the whole literal.
  JoinedStr(raw~ : String, span~ : @basic.Span)
  Constant(value~ : Constant, span~ : @basic.Span)
  Attribute(
    value~ : Expr,
    attr~ : String,
    ctx~ : ExprContext,
    span~ : @basic.Span
  )
  Subscript(
    value~ : Expr,
    slice~ : Expr,
    ctx~ : ExprContext,
    span~ : @basic.Span
  )
  Starred(value~ : Expr, ctx~ : ExprContext, span~ : @basic.Span)
  Name(id~ : String, ctx~ : ExprContext, span~ : @basic.Span)
  List(elts~ : Array[Expr], ctx~ : ExprContext, span~ : @basic.Span)
  Tuple(elts~ : Array[Expr], ctx~ : ExprContext, span~ : @basic.Span)
  Slice(lower~ : Expr?, upper~ : Expr?, step~ : Expr?, span~ : @basic.Span)
} derive(Eq, Debug)

///|
/// A literal's value. `Complex` keeps only the imaginary part: a Python
/// complex LITERAL is always `0+bj`, and the sieve rejects it before anything
/// needs the real part.
pub(all) enum Constant {
  Int(BigInt)
  Float(Double)
  Complex(Double)
  Str(String)
  Bytes(Bytes)
  Bool(Bool)
  None
  Ellipsis
} derive(Eq, Debug)

///|
pub(all) enum ExprContext {
  Load
  Store
  Del
} derive(Eq, Debug)

///|
pub(all) enum BoolOp {
  And
  Or
} derive(Eq, Debug)

///|
pub(all) enum Operator {
  Add
  Sub
  Mult
  MatMult
  Div
  Mod
  Pow
  LShift
  RShift
  BitOr
  BitXor
  BitAnd
  FloorDiv
} derive(Eq, Debug)

///|
pub(all) enum UnaryOp {
  Invert
  Not
  UAdd
  USub
} derive(Eq, Debug)

///|
pub(all) enum CmpOp {
  Eq
  NotEq
  Lt
  LtE
  Gt
  GtE
  Is
  IsNot
  In
  NotIn
} derive(Eq, Debug)

///|
pub(all) struct Comprehension {
  target : Expr
  iter : Expr
  ifs : Array[Expr]
  is_async : Bool
} derive(Eq, Debug)

///|
pub(all) struct ExceptHandler {
  type_ : Expr?
  name : String?
  body : Array[Stmt]
  span : @basic.Span
} derive(Eq, Debug)

///|
pub(all) struct Arguments {
  posonlyargs : Array[Arg]
  args : Array[Arg]
  vararg : Arg?
  kwonlyargs : Array[Arg]
  kw_defaults : Array[Expr?]
  kwarg : Arg?
  defaults : Array[Expr]
} derive(Eq, Debug)

///|
pub(all) struct Arg {
  arg : String
  annotation : Expr?
  span : @basic.Span
} derive(Eq, Debug)

///|
/// A keyword argument. `arg` of `None` is `**kwargs`.
pub(all) struct Keyword {
  arg : String?
  value : Expr
  span : @basic.Span
} derive(Eq, Debug)

///|
pub(all) struct Alias {
  name : String
  asname : String?
  span : @basic.Span
} derive(Eq, Debug)

///|
pub(all) struct WithItem {
  context_expr : Expr
  optional_vars : Expr?
} derive(Eq, Debug)

///|
pub(all) struct MatchCase {
  pattern : Pattern
  guard_ : Expr?
  body : Array[Stmt]
} derive(Eq, Debug)

///|
/// Which bracket a sequence pattern was written with. CPython forgets; the
/// spec distinguishes `[p]` from `(p)`, and a bare `case a, b:` is a tuple.
pub(all) enum SeqKind {
  List
  Tuple
} derive(Eq, Debug)

///|
pub(all) enum Pattern {
  /// A literal, a negated literal, a complex literal, or a dotted name.
  MatchValue(value~ : Expr, span~ : @basic.Span)
  /// `None`, `True` or `False`, which match by identity.
  MatchSingleton(value~ : Constant, span~ : @basic.Span)
  MatchSequence(
    kind~ : SeqKind,
    patterns~ : Array[Pattern],
    span~ : @basic.Span
  )
  MatchMapping(
    keys~ : Array[Expr],
    patterns~ : Array[Pattern],
    rest~ : String?,
    span~ : @basic.Span
  )
  MatchClass(
    cls~ : Expr,
    patterns~ : Array[Pattern],
    kwd_attrs~ : Array[String],
    kwd_patterns~ : Array[Pattern],
    span~ : @basic.Span
  )
  MatchStar(name~ : String?, span~ : @basic.Span)
  /// `_` is `MatchAs(None, None)`; `x` is `MatchAs(None, Some("x"))`.
  MatchAs(pattern~ : Pattern?, name~ : String?, span~ : @basic.Span)
  MatchOr(patterns~ : Array[Pattern], span~ : @basic.Span)
} derive(Eq, Debug)