///|
/// Constructors for a code generator.
///
/// A generator builds a tree and prints it with `write`; it has no source, so
/// it has no positions. Every builder here supplies `@basic.nowhere` and
/// sensible defaults, so that `Expr::call(Expr::name("print"), [Expr::str("hi")])`
/// is a whole statement's worth of tree.
///
/// The builders are also what keeps `ast` honest as a package a consumer can
/// use alone: `tools/embed-smoke.sh` builds a program from them and checks
/// that doing so links neither the lexer, the parser, the checker nor the
/// evaluator.
pub fn Expr::name(id : String, ctx? : ExprContext = Load) -> Expr {
  Name(id~, ctx~, span=@basic.nowhere)
}

///|
pub fn Expr::int(n : Int) -> Expr {
  Constant(value=Int(BigInt::from_int(n)), span=@basic.nowhere)
}

///|
pub fn Expr::bigint(n : BigInt) -> Expr {
  Constant(value=Int(n), span=@basic.nowhere)
}

///|
pub fn Expr::float(d : Double) -> Expr {
  Constant(value=Float(d), span=@basic.nowhere)
}

///|
pub fn Expr::str(s : String) -> Expr {
  Constant(value=Str(s), span=@basic.nowhere)
}

///|
pub fn Expr::bool(b : Bool) -> Expr {
  Constant(value=Bool(b), span=@basic.nowhere)
}

///|
pub fn Expr::none() -> Expr {
  Constant(value=None, span=@basic.nowhere)
}

///|
pub fn Expr::call(
  func : Expr,
  args : Array[Expr],
  keywords? : Array[Keyword] = [],
) -> Expr {
  Call(func~, args~, keywords~, span=@basic.nowhere)
}

///|
pub fn Expr::attribute(
  value : Expr,
  attr : String,
  ctx? : ExprContext = Load,
) -> Expr {
  Attribute(value~, attr~, ctx~, span=@basic.nowhere)
}

///|
pub fn Expr::subscript(
  value : Expr,
  slice : Expr,
  ctx? : ExprContext = Load,
) -> Expr {
  Subscript(value~, slice~, ctx~, span=@basic.nowhere)
}

///|
pub fn Expr::binop(left : Expr, op : Operator, right : Expr) -> Expr {
  BinOp(left~, op~, right~, span=@basic.nowhere)
}

///|
pub fn Expr::compare(left : Expr, op : CmpOp, right : Expr) -> Expr {
  Compare(left~, ops=[op], comparators=[right], span=@basic.nowhere)
}

///|
pub fn Expr::bool_op(op : BoolOp, values : Array[Expr]) -> Expr {
  BoolOp(op~, values~, span=@basic.nowhere)
}

///|
pub fn Expr::not_(e : Expr) -> Expr {
  UnaryOp(op=Not, operand=e, span=@basic.nowhere)
}

///|
pub fn Expr::neg(e : Expr) -> Expr {
  UnaryOp(op=USub, operand=e, span=@basic.nowhere)
}

///|
pub fn Expr::if_exp(cond : Expr, body : Expr, or_else : Expr) -> Expr {
  IfExp(cond~, body~, or_else~, span=@basic.nowhere)
}

///|
pub fn Expr::list(elts : Array[Expr], ctx? : ExprContext = Load) -> Expr {
  List(elts~, ctx~, span=@basic.nowhere)
}

///|
pub fn Expr::tuple(elts : Array[Expr], ctx? : ExprContext = Load) -> Expr {
  Tuple(elts~, ctx~, span=@basic.nowhere)
}

///|
pub fn Expr::dict(entries : Array[(Expr, Expr)]) -> Expr {
  Dict(
    keys=entries.map(fn(e) { Some(e.0) }),
    values=entries.map(fn(e) { e.1 }),
    span=@basic.nowhere,
  )
}

///|
pub fn Expr::lambda(params : Array[String], body : Expr) -> Expr {
  Lambda(args=arguments(params), body~, span=@basic.nowhere)
}

///|
/// A plain parameter list: names, no defaults, no annotations, no stars.
pub fn arguments(params : Array[String]) -> Arguments {
  {
    posonlyargs: [],
    args: params.map(fn(p) {
      { arg: p, annotation: None, span: @basic.nowhere, }
    }),
    vararg: None,
    kwonlyargs: [],
    kw_defaults: [],
    kwarg: None,
    defaults: [],
  }
}

///|
pub fn Stmt::expr_stmt(e : Expr) -> Stmt {
  ExprStmt(value=e, span=@basic.nowhere)
}

///|
pub fn Stmt::assign(target : String, value : Expr) -> Stmt {
  Assign(targets=[Expr::name(target, ctx=Store)], value~, span=@basic.nowhere)
}

///|
pub fn Stmt::return_(value? : Expr) -> Stmt {
  Return(value~, span=@basic.nowhere)
}

///|
pub fn Stmt::pass() -> Stmt {
  Pass(span=@basic.nowhere)
}

///|
pub fn Stmt::if_(
  cond : Expr,
  body : Array[Stmt],
  or_else? : Array[Stmt] = [],
) -> Stmt {
  If(cond~, body~, or_else~, span=@basic.nowhere)
}

///|
pub fn Stmt::def_(
  name : String,
  params : Array[String],
  body : Array[Stmt],
) -> Stmt {
  FunctionDef(
    name~,
    args=arguments(params),
    body~,
    decorators=[],
    returns=None,
    is_async=false,
    span=@basic.nowhere,
  )
}

///|
/// A PurePy dataclass: `@dataclass` on a class whose body is `x: Any` for
/// each field, with an optional base class. That is the only class form
/// PurePy accepts, so it is the only one the builder offers.
pub fn Stmt::dataclass(
  name : String,
  fields : Array[String],
  base? : String,
) -> Stmt {
  ClassDef(
    name~,
    bases=match base {
      None => []
      Some(b) => [Expr::name(b)]
    },
    keywords=[],
    body=fields.map(fn(f) {
      Stmt::AnnAssign(
        target=Expr::name(f, ctx=Store),
        annotation=Expr::name("Any"),
        value=None,
        simple=true,
        span=@basic.nowhere,
      )
    }),
    decorators=[Expr::name("dataclass")],
    span=@basic.nowhere,
  )
}

///|
pub fn Stmt::match_(subject : Expr, cases : Array[MatchCase]) -> Stmt {
  Match(subject~, cases~, span=@basic.nowhere)
}

///|
pub fn Stmt::import_(names : Array[String]) -> Stmt {
  Import(
    names=names.map(fn(n) { { name: n, asname: None, span: @basic.nowhere, } }),
    span=@basic.nowhere,
  )
}

///|
pub fn Stmt::from_import(module_name : String, names : Array[String]) -> Stmt {
  ImportFrom(
    module_name=Some(module_name),
    names=names.map(fn(n) { { name: n, asname: None, span: @basic.nowhere, } }),
    level=0,
    span=@basic.nowhere,
  )
}

///|
pub fn case_(pattern : Pattern, body : Array[Stmt], when? : Expr) -> MatchCase {
  { pattern, guard_: when, body, }
}

///|
pub fn Pattern::capture(name : String) -> Pattern {
  MatchAs(pattern=None, name=Some(name), span=@basic.nowhere)
}

///|
pub fn Pattern::wildcard() -> Pattern {
  MatchAs(pattern=None, name=None, span=@basic.nowhere)
}

///|
pub fn Pattern::literal(c : Constant) -> Pattern {
  MatchValue(value=Constant(value=c, span=@basic.nowhere), span=@basic.nowhere)
}

///|
pub fn Pattern::constr(
  cls : String,
  patterns : Array[Pattern],
  keywords? : Array[(String, Pattern)] = [],
) -> Pattern {
  MatchClass(
    cls=Expr::name(cls),
    patterns~,
    kwd_attrs=keywords.map(fn(k) { k.0 }),
    kwd_patterns=keywords.map(fn(k) { k.1 }),
    span=@basic.nowhere,
  )
}

///|
pub fn Pattern::sequence(kind : SeqKind, patterns : Array[Pattern]) -> Pattern {
  MatchSequence(kind~, patterns~, span=@basic.nowhere)
}

///|
pub fn module_of(body : Array[Stmt]) -> Module {
  { body, span: @basic.nowhere, }
}