///|
/// Statement evaluation: Figures 4.3 and 4.4.
///
/// A sequence's result is the bindings the SEQUENCE made, not the whole
/// environment: `eval-seq` composes `assigns ρ'` with what follows, and the
/// caller overrides its own environment with the result. That is what keeps a
/// function body's locals out of its caller.
pub async fn Interp::eval_seq(
  self : Interp,
  items : ArrayView[@analysis.Statement],
  env : Env,
) -> StmtResult noraise {
  self.machine_seq(items, env)
}

///|
pub async fn Interp::eval_body(
  self : Interp,
  body : Array[@ast.Stmt],
  env : Env,
) -> StmtResult noraise {
  self.machine_seq(@analysis.statements(body)[:], env)
}

///|
/// Match a destructuring target against a value, collecting what it binds.
///
/// Answers `None` when it bound everything and the reason when it could not.
/// Python raises `ValueError` for an arity mismatch and `TypeError` for a
/// value with no elements; PurePy models neither as a termination, so both are
/// undefined here rather than borrowing a kind that means something else.
///
/// Bindings are collected into one array rather than merged environment by
/// environment, so a repeated name -- `a, a = 1, 2`, which Python allows and
/// leaves at 2 -- resolves left to right the way Python resolves it.
fn destructure(
  target : @ast.Expr,
  v : Value,
  out : Array[(String, Value)],
) -> String? {
  match target {
    Name(id~, ..) => {
      out.push((id, v))
      None
    }
    Tuple(elts~, ..) | List(elts~, ..) => {
      let xs = match @value.elems(v) {
        Some(xs) => xs
        None => return Some("unpacking " + v.kind_name())
      }
      if xs.length() != elts.length() {
        return Some(
          "unpacking \{xs.length()} values into \{elts.length()} targets",
        )
      }
      for i, t in elts {
        match destructure(t, xs[i], out) {
          None => ()
          some => return some
        }
      }
      None
    }
    _ => Some("an assignment target that is not a name")
  }
}