///|
/// Generator runtime (minimal, resumable).

///|
priv enum GenFrame {
  Block(body~ : Array[Stmt], index~ : Int32, loop_depth~ : Int32)
  // Marker pushed when yielding to the caller. Needed so `throw()` can raise
  // into the suspended generator at the yield point (contextlib.contextmanager).
  AfterYield
  AfterAssign(target~ : Target, value~ : Value)
  For(
    target~ : Target,
    iterator~ : Value,
    body~ : Array[Stmt],
    else_body~ : Array[Stmt],
    broke~ : Bool,
    done~ : Bool,
    loop_depth~ : Int32
  )
  While(
    condition~ : Expr,
    body~ : Array[Stmt],
    else_body~ : Array[Stmt],
    broke~ : Bool,
    done~ : Bool,
    loop_depth~ : Int32
  )
  YieldFrom(iterator~ : Value)
  WithExit(exit~ : Value)
  Try(
    handlers~ : Array[ExceptHandler],
    else_body~ : Array[Stmt],
    finally_body~ : Array[Stmt],
    phase~ : Int32,
    pending~ : RuntimeError?,
    handler_active~ : Bool,
    loop_depth~ : Int32
  )
}

///|
priv struct GeneratorState {
  mut frames : Array[GenFrame]
  locals : Array[(String, Value)]
  globals : Array[(String, Value)]
  builtins : Array[(String, Value)]
  io : MockIO
  global_names : Array[String]
  nonlocal_names : Array[String]
  closure : Array[(String, Value)]
  name : String
  filename : String
  is_async : Bool
  mut started : Bool
  mut done : Bool
}

///|
let generator_registry : Ref[Array[Ref[GeneratorState]]] = { val: [], }

///|
fn make_generator_instance(id : Int32) -> Value {
  Value::Instance(InstanceValue::{
    class: ClassValue::{ name: "generator", bases: [], dict: [], },
    dict: [("id", Value::Int(@bigint.BigInt::from_int(id)))],
  })
}

///|
fn make_async_generator_instance(id : Int32) -> Value {
  Value::Instance(InstanceValue::{
    class: ClassValue::{ name: "async_generator", bases: [], dict: [], },
    dict: [("gen_id", Value::Int(@bigint.BigInt::from_int(id)))],
  })
}

///|
fn generator_id_from_instance(
  inst : InstanceValue,
) -> Result[Int32?, RuntimeError] {
  match get_named_value(inst.dict, "id") {
    Some(Value::Int(v)) =>
      match bigint_to_int_checked(v) {
        Ok(i) => Ok(Some(i))
        Err(err) => Err(err)
      }
    Some(Value::Bool(v)) => Ok(Some(if v { 1 } else { 0 }))
    _ => Ok(None)
  }
}

///|
fn async_generator_id_from_instance(
  inst : InstanceValue,
) -> Result[Int32?, RuntimeError] {
  match get_named_value(inst.dict, "gen_id") {
    Some(Value::Int(v)) =>
      match bigint_to_int_checked(v) {
        Ok(i) => Ok(Some(i))
        Err(err) => Err(err)
      }
    Some(Value::Bool(v)) => Ok(Some(if v { 1 } else { 0 }))
    _ => Ok(None)
  }
}

///|
fn generator_new(
  body : Array[Stmt],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
  global_names : Array[String],
  nonlocal_names : Array[String],
  closure : Array[(String, Value)],
  name : String,
  filename : String,
) -> Value {
  let frames : Array[GenFrame] = [GenFrame::Block(body~, index=0, loop_depth=0)]
  let state : Ref[GeneratorState] = {
    val: GeneratorState::{
      frames,
      locals,
      globals,
      builtins,
      io,
      global_names,
      nonlocal_names,
      closure,
      name,
      filename,
      is_async: false,
      started: false,
      done: false,
    },
  }
  let id = generator_registry.val.length()
  generator_registry.val.push(state)
  make_generator_instance(id)
}

///|
fn async_generator_new(
  body : Array[Stmt],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
  global_names : Array[String],
  nonlocal_names : Array[String],
  closure : Array[(String, Value)],
  name : String,
  filename : String,
) -> Value {
  let frames : Array[GenFrame] = [GenFrame::Block(body~, index=0, loop_depth=0)]
  let state : Ref[GeneratorState] = {
    val: GeneratorState::{
      frames,
      locals,
      globals,
      builtins,
      io,
      global_names,
      nonlocal_names,
      closure,
      name,
      filename,
      is_async: true,
      started: false,
      done: false,
    },
  }
  let id = generator_registry.val.length()
  generator_registry.val.push(state)
  make_async_generator_instance(id)
}

///|
fn is_stop_iteration(err : RuntimeError) -> Bool {
  err.exc_type == "StopIteration"
}

///|
fn generator_stop_iteration() -> RuntimeError {
  make_runtime_error(RuntimeErrorKind::Runtime, "StopIteration".to_string())
}

///|
fn generator_stop_iteration_with_value(value : Value) -> RuntimeError {
  let err = generator_stop_iteration()
  RuntimeError::{
    kind: err.kind,
    message: err.message,
    span: err.span,
    traceback: err.traceback,
    traceback_envs: err.traceback_envs,
    exc_type: err.exc_type,
    exc_args: err.exc_args,
    exc_value: Some(value),
    exc_cause: err.exc_cause,
    exc_context: err.exc_context,
    exc_suppress_context: err.exc_suppress_context,
  }
}

///|
fn generator_state_from_value(
  generator_value : Value,
) -> Result[Ref[GeneratorState], RuntimeError] {
  match generator_value {
    Value::Instance(inst) =>
      if inst.class.name != "generator" {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "'" + inst.class.name + "' object is not a generator",
          ),
        )
      } else {
        let id_opt = match generator_id_from_instance(inst) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let id = match id_opt {
          Some(id) => id
          None =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "RuntimeError: invalid generator".to_string(),
              ),
            )
        }
        if id < 0 || id >= generator_registry.val.length() {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Runtime,
              "RuntimeError: invalid generator id".to_string(),
            ),
          )
        }
        Ok(generator_registry.val[id])
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "object is not a generator".to_string(),
        ),
      )
  }
}

///|
fn async_generator_state_from_value(
  generator_value : Value,
) -> Result[Ref[GeneratorState], RuntimeError] {
  match generator_value {
    Value::Instance(inst) =>
      if inst.class.name != "async_generator" {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "'" + inst.class.name + "' object is not an async generator",
          ),
        )
      } else {
        let id_opt = match async_generator_id_from_instance(inst) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let id = match id_opt {
          Some(id) => id
          None =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "RuntimeError: invalid async generator".to_string(),
              ),
            )
        }
        if id < 0 || id >= generator_registry.val.length() {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Runtime,
              "RuntimeError: invalid async generator id".to_string(),
            ),
          )
        }
        Ok(generator_registry.val[id])
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "object is not an async generator".to_string(),
        ),
      )
  }
}

///|
fn generator_unwind_to_loop(
  frames : Array[GenFrame],
  is_break : Bool,
) -> Result[Unit, RuntimeError] {
  let mut i = frames.length()
  while i > 0 {
    i = i - 1
    match frames[i] {
      GenFrame::For(
        target~,
        iterator~,
        body~,
        else_body~,
        broke~,
        done~,
        loop_depth~
      ) => {
        let _ = broke
        let _ = done
        // Drop everything above the loop frame.
        while frames.length() > i + 1 {
          let _ = frames.pop()
        }
        if is_break {
          frames[i] = GenFrame::For(
            target~,
            iterator~,
            body~,
            else_body~,
            broke=true,
            done=true,
            loop_depth~,
          )
        }
        // For `continue`, no special state: execution resumes at loop header.
        return Ok(())
      }
      GenFrame::While(condition~, body~, else_body~, broke~, done~, loop_depth~) => {
        let _ = broke
        let _ = done
        while frames.length() > i + 1 {
          let _ = frames.pop()
        }
        if is_break {
          frames[i] = GenFrame::While(
            condition~,
            body~,
            else_body~,
            broke=true,
            done=true,
            loop_depth~,
          )
        }
        return Ok(())
      }
      _ => ()
    }
  }
  Err(
    make_runtime_error(
      RuntimeErrorKind::Runtime,
      if is_break {
        "SyntaxError: 'break' outside loop".to_string()
      } else {
        "SyntaxError: 'continue' not properly in loop".to_string()
      },
    ),
  )
}

///|
fn generator_step_statement(
  stmt : Stmt,
  state : Ref[GeneratorState],
  loop_depth : Int32,
) -> Result[Value?, RuntimeError] {
  match stmt {
    Stmt::WithSpan(span~, stmt~) => {
      push_active_span(span)
      let result = generator_step_statement(stmt, state, loop_depth)
      pop_active_span()
      result
    }
    Stmt::ExprStmt(expr) => {
      match expr {
        Expr::YieldFrom(inner) => {
          let iter_value = match
            eval_expr_with_env(
              inner,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          let iterator = match
            iter_value_to_iterator(
              iter_value,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          state.val.frames.push(GenFrame::YieldFrom(iterator~))
          return Ok(None)
        }
        Expr::Yield(expr_opt) => {
          let value = match expr_opt {
            Some(inner) =>
              match
                eval_expr_with_env(
                  inner,
                  state.val.locals,
                  state.val.globals,
                  state.val.builtins,
                  state.val.io,
                ) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
            None => Value::None
          }
          state.val.frames.push(GenFrame::AfterYield)
          return Ok(Some(value))
        }
        _ => ()
      }
      let _ = match
        eval_expr_with_env(
          expr,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      Ok(None)
    }
    Stmt::Assign(target~, value~) => {
      match value {
        Expr::YieldFrom(inner) => {
          let iter_value = match
            eval_expr_with_env(
              inner,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          let iterator = match
            iter_value_to_iterator(
              iter_value,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          state.val.frames.push(
            GenFrame::AfterAssign(target~, value=Value::None),
          )
          state.val.frames.push(GenFrame::YieldFrom(iterator~))
          return Ok(None)
        }
        Expr::Yield(expr_opt) => {
          let yield_value = match expr_opt {
            Some(inner) =>
              match
                eval_expr_with_env(
                  inner,
                  state.val.locals,
                  state.val.globals,
                  state.val.builtins,
                  state.val.io,
                ) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
            None => Value::None
          }
          state.val.frames.push(
            GenFrame::AfterAssign(target~, value=Value::None),
          )
          return Ok(Some(yield_value))
        }
        _ => ()
      }
      let eval_value = match
        eval_expr_with_env(
          value,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let _ = match
        assign_target_with_env(
          target,
          eval_value,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      Ok(None)
    }
    Stmt::AnnAssign(target~, annotation~, value~) => {
      match value {
        Some(value_expr) => {
          match value_expr {
            Expr::YieldFrom(inner) => {
              let iter_value = match
                eval_expr_with_env(
                  inner,
                  state.val.locals,
                  state.val.globals,
                  state.val.builtins,
                  state.val.io,
                ) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
              let iterator = match
                iter_value_to_iterator(
                  iter_value,
                  state.val.globals,
                  state.val.builtins,
                  state.val.io,
                ) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
              state.val.frames.push(
                GenFrame::AfterAssign(target~, value=Value::None),
              )
              state.val.frames.push(GenFrame::YieldFrom(iterator~))
              return Ok(None)
            }
            Expr::Yield(expr_opt) => {
              let yield_value = match expr_opt {
                Some(inner) =>
                  match
                    eval_expr_with_env(
                      inner,
                      state.val.locals,
                      state.val.globals,
                      state.val.builtins,
                      state.val.io,
                    ) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  }
                None => Value::None
              }
              state.val.frames.push(
                GenFrame::AfterAssign(target~, value=Value::None),
              )
              return Ok(Some(yield_value))
            }
            _ => ()
          }
          let eval_value = match
            eval_expr_with_env(
              value_expr,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          let _ = match
            assign_target_with_env(
              target,
              eval_value,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          ()
        }
        None => ()
      }
      match target {
        Target::Name(name) => {
          // Only simple-name annotations contribute to __annotations__. For
          // attribute/subscript targets, ignore the annotation expression.
          let ann_value = match
            eval_expr_with_env(
              annotation,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          let pairs = match
            get_scoped_value(
              state.val.locals,
              state.val.globals,
              "__annotations__",
            ) {
            Some(Value::Dict(pairs)) => pairs
            Some(_) =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "__annotations__ must be a dict".to_string(),
                ),
              )
            None => {
              let pairs : Array[(Value, Value)] = []
              set_scoped_value(
                state.val.locals,
                state.val.globals,
                "__annotations__",
                Value::Dict(pairs),
              )
              pairs
            }
          }
          let _ = match dict_set_item(pairs, Value::Str(name), ann_value) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
        }
        _ => ()
      }
      Ok(None)
    }
    Stmt::AugAssign(target~, op~, value~) => {
      let right_val = match
        eval_expr_with_env(
          value,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      match target {
        Target::Name(name) => {
          let current = match
            lookup_name_value(
              name,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          let next = match
            eval_augassign_op_values(
              op,
              current,
              right_val,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          set_scoped_value(state.val.locals, state.val.globals, name, next)
          Ok(None)
        }
        _ =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::NotImplemented,
              "augmented assignment target not supported in generator".to_string(),
            ),
          )
      }
    }
    Stmt::Return(expr_opt) => {
      let value = match expr_opt {
        Some(expr) =>
          match
            eval_expr_with_env(
              expr,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
        None => Value::None
      }
      Err(generator_stop_iteration_with_value(value))
    }
    Stmt::Yield(expr_opt) => {
      let value = match expr_opt {
        Some(expr) =>
          match
            eval_expr_with_env(
              expr,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
        None => Value::None
      }
      state.val.frames.push(GenFrame::AfterYield)
      Ok(Some(value))
    }
    Stmt::YieldFrom(expr) => {
      let iter_value = match
        eval_expr_with_env(
          expr,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let iterator = match
        iter_value_to_iterator(
          iter_value,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      state.val.frames.push(GenFrame::YieldFrom(iterator~))
      Ok(None)
    }
    Stmt::If(condition~, body~, else_body~) => {
      let cond_value = match
        eval_expr_with_env(
          condition,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let ok = match
        truthy_from_value_with_env(
          cond_value,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      if ok {
        state.val.frames.push(GenFrame::Block(body~, index=0, loop_depth~))
      } else if else_body.length() > 0 {
        state.val.frames.push(
          GenFrame::Block(body=else_body, index=0, loop_depth~),
        )
      }
      Ok(None)
    }
    Stmt::While(condition~, body~, else_body~) => {
      state.val.frames.push(
        GenFrame::While(
          condition~,
          body~,
          else_body~,
          broke=false,
          done=false,
          loop_depth~,
        ),
      )
      Ok(None)
    }
    Stmt::For(target~, iter~, body~, else_body~) => {
      let iter_value = match
        eval_expr_with_env(
          iter,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let iterator = match
        iter_value_to_iterator(
          iter_value,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      state.val.frames.push(
        GenFrame::For(
          target~,
          iterator~,
          body~,
          else_body~,
          broke=false,
          done=false,
          loop_depth~,
        ),
      )
      Ok(None)
    }
    Stmt::Break =>
      if loop_depth > 0 {
        let _ = match generator_unwind_to_loop(state.val.frames, true) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        Ok(None)
      } else {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Runtime,
            "SyntaxError: 'break' outside loop".to_string(),
          ),
        )
      }
    Stmt::Continue =>
      if loop_depth > 0 {
        let _ = match generator_unwind_to_loop(state.val.frames, false) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        Ok(None)
      } else {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Runtime,
            "SyntaxError: 'continue' not properly in loop".to_string(),
          ),
        )
      }
    Stmt::Import(module_name~, alias_name~) => {
      let module_value = match
        import_module(
          module_name,
          state.val.globals,
          state.val.builtins,
          state.val.io,
          current_config(),
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let binding = match alias_name {
        Some(name) => name
        None => module_top_name(module_name)
      }
      let binding_value = match alias_name {
        Some(_) => module_value
        None =>
          if module_name.contains(".") {
            match module_cache_get(binding) {
              Some(value) => value
              None => module_value
            }
          } else {
            module_value
          }
      }
      set_scoped_value(
        state.val.locals,
        state.val.globals,
        binding,
        binding_value,
      )
      Ok(None)
    }
    Stmt::FromImport(module_name~, names~) => {
      let resolved_name = match
        resolve_relative_module_name(module_name, state.val.globals) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let module_value = match
        import_module(
          resolved_name,
          state.val.globals,
          state.val.builtins,
          state.val.io,
          current_config(),
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      for item in names {
        let name = item.0
        let alias_opt = item.1
        if name == "*" {
          match module_value {
            Value::Instance(inst) =>
              for pair in inst.dict {
                if pair.0 != "__name__" && !pair.0.has_prefix("_") {
                  set_scoped_value(
                    state.val.locals,
                    state.val.globals,
                    pair.0,
                    pair.1,
                  )
                }
              }
            _ => ()
          }
          continue
        }
        let imported = match
          get_attr_from_value(
            module_value,
            name,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(value) => value
          Err(err) =>
            match err.kind {
              RuntimeErrorKind::Attribute => {
                let submodule = resolved_name + "." + name
                match
                  import_module(
                    submodule,
                    state.val.globals,
                    state.val.builtins,
                    state.val.io,
                    current_config(),
                  ) {
                  Ok(value) => value
                  Err(_) =>
                    return Err(
                      make_runtime_error(
                        RuntimeErrorKind::Runtime,
                        "ImportError: cannot import name '" +
                        name +
                        "' from '" +
                        resolved_name +
                        "'",
                      ),
                    )
                }
              }
              _ => return Err(err)
            }
        }
        let binding = match alias_opt {
          Some(alias_name) => alias_name
          None => name
        }
        set_scoped_value(state.val.locals, state.val.globals, binding, imported)
      }
      Ok(None)
    }
    Stmt::Pass | Stmt::Global(_) | Stmt::Nonlocal(_) => Ok(None)
    Stmt::With(context~, target~, body~) => {
      let ctx_value = match
        eval_expr_with_env(
          context,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let enter_value = match
        get_attr_from_value(
          ctx_value,
          "__enter__",
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let entered = match
        call_callable_with_env(
          enter_value,
          [],
          [],
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      match target {
        Some(name) =>
          set_scoped_value(state.val.locals, state.val.globals, name, entered)
        None => ()
      }
      let exit_value = match
        get_attr_from_value(
          ctx_value,
          "__exit__",
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      state.val.frames.push(GenFrame::WithExit(exit=exit_value))
      state.val.frames.push(GenFrame::Block(body~, index=0, loop_depth~))
      Ok(None)
    }
    Stmt::Assert(condition~, message~) => {
      let value = match
        eval_expr_with_env(
          condition,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let ok = match
        truthy_from_value_with_env(
          value,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      if !ok {
        let text = match message {
          Some(expr) => {
            let msg_value = match
              eval_expr_with_env(
                expr,
                state.val.locals,
                state.val.globals,
                state.val.builtins,
                state.val.io,
              ) {
              Ok(value) => value
              Err(err) => return Err(err)
            }
            "AssertionError: " + value_to_string(msg_value)
          }
          None => "AssertionError: ".to_string()
        }
        return Err(make_runtime_error(RuntimeErrorKind::Runtime, text))
      }
      Ok(None)
    }
    Stmt::Raise(exc~, cause~) =>
      match exc {
        None =>
          match active_exception() {
            Some(err) => Err(err)
            None =>
              Err(
                make_runtime_error(
                  RuntimeErrorKind::Runtime,
                  "RuntimeError: No active exception to reraise".to_string(),
                ),
              )
          }
        Some(expr) => {
          let context_err_opt = active_exception()
          let raised_value = match
            eval_expr_with_env(
              expr,
              state.val.locals,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          let inst_value = match
            ensure_exception_instance_from_value(
              raised_value,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            ) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          let err0 = match inst_value {
            Value::Instance(inst) => runtime_error_from_exception_instance(inst)
            _ =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Runtime,
                  "RuntimeError: invalid exception".to_string(),
                ),
              )
          }
          let err1 = match (context_err_opt, err0.exc_context) {
            (Some(ctx_err), None) => {
              let ctx_value = exception_value_from_runtime_error(
                ctx_err,
                state.val.globals,
                state.val.builtins,
                state.val.io,
              )
              RuntimeError::{
                kind: err0.kind,
                message: err0.message,
                span: err0.span,
                traceback: err0.traceback,
                traceback_envs: err0.traceback_envs,
                exc_type: err0.exc_type,
                exc_args: err0.exc_args,
                exc_value: err0.exc_value,
                exc_cause: err0.exc_cause,
                exc_context: Some(ctx_value),
                exc_suppress_context: err0.exc_suppress_context,
              }
            }
            _ => err0
          }
          let err2 = match cause {
            None => err1
            Some(cause_expr) => {
              let cause_value = match
                eval_expr_with_env(
                  cause_expr,
                  state.val.locals,
                  state.val.globals,
                  state.val.builtins,
                  state.val.io,
                ) {
                Ok(value) => value
                Err(e) => return Err(e)
              }
              let cause_inst = match cause_value {
                Value::None => Value::None
                _ =>
                  match
                    ensure_exception_instance_from_value(
                      cause_value,
                      state.val.globals,
                      state.val.builtins,
                      state.val.io,
                    ) {
                    Ok(v) => v
                    Err(e) => return Err(e)
                  }
              }
              RuntimeError::{
                kind: err1.kind,
                message: err1.message,
                span: err1.span,
                traceback: err1.traceback,
                traceback_envs: err1.traceback_envs,
                exc_type: err1.exc_type,
                exc_args: err1.exc_args,
                exc_value: err1.exc_value,
                exc_cause: Some(cause_inst),
                exc_context: err1.exc_context,
                exc_suppress_context: true,
              }
            }
          }
          Err(err2)
        }
      }
    Stmt::Match(subject~, cases~) => {
      let subject_value = match
        eval_expr_with_env(
          subject,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      for case in cases {
        let pattern = case.0
        let guard_expr_opt : Expr? = case.1
        let case_body = case.2
        match match_pattern(subject_value, pattern) {
          Some(bindings) => {
            let previous : Array[(String, Value?)] = []
            for binding in bindings {
              previous.push(
                (
                  binding.0,
                  get_scoped_value(
                    state.val.locals,
                    state.val.globals,
                    binding.0,
                  ),
                ),
              )
              set_scoped_value(
                state.val.locals,
                state.val.globals,
                binding.0,
                binding.1,
              )
            }
            let mut guard_ok = true
            if guard_expr_opt is Some(guard_expr) {
              let guard_value = match
                eval_expr_with_env(
                  guard_expr,
                  state.val.locals,
                  state.val.globals,
                  state.val.builtins,
                  state.val.io,
                ) {
                Ok(value) => value
                Err(err) => return Err(err)
              }
              guard_ok = match
                truthy_from_value_with_env(
                  guard_value,
                  state.val.globals,
                  state.val.builtins,
                  state.val.io,
                ) {
                Ok(v) => v
                Err(err) => return Err(err)
              }
            }
            if guard_ok {
              state.val.frames.push(
                GenFrame::Block(body=case_body, index=0, loop_depth~),
              )
              return Ok(None)
            }
            // Guard failed: rollback bindings before trying the next case.
            for prev in previous {
              if prev.1 is Some(value) {
                set_scoped_value(
                  state.val.locals,
                  state.val.globals,
                  prev.0,
                  value,
                )
              } else {
                let _ = delete_scoped_value(
                  state.val.locals,
                  state.val.globals,
                  prev.0,
                )
              }
            }
          }
          None => ()
        }
      }
      Ok(None)
    }
    Stmt::Try(body~, handlers~, else_body~, finally_body~) => {
      // except* (PEP 654) is not supported in the generator interpreter yet.
      for h in handlers {
        if h.is_star {
          return Err(
            make_runtime_error(
              RuntimeErrorKind::NotImplemented,
              "except* not implemented in generator".to_string(),
            ),
          )
        }
      }
      state.val.frames.push(
        GenFrame::Try(
          handlers~,
          else_body~,
          finally_body~,
          phase=0,
          pending=None,
          handler_active=false,
          loop_depth~,
        ),
      )
      state.val.frames.push(GenFrame::Block(body~, index=0, loop_depth~))
      Ok(None)
    }
    Stmt::Del(targets) => {
      for target in targets {
        let _ = match
          delete_target_with_env(
            target,
            state.val.locals,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
      }
      Ok(None)
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::NotImplemented,
          "statement not implemented in generator".to_string(),
        ),
      )
  }
}

///|
fn generator_try_handler_matches(
  err : RuntimeError,
  handler : ExceptHandler,
  state : Ref[GeneratorState],
) -> Result[Bool, RuntimeError] {
  if handler.is_star {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::NotImplemented,
        "except* not implemented in generator".to_string(),
      ),
    )
  }
  match handler.exc {
    None => Ok(true)
    Some(expr) => {
      let exc_value = match
        eval_expr_with_env(
          expr,
          state.val.locals,
          state.val.globals,
          state.val.builtins,
          state.val.io,
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      match exc_value {
        Value::Class(klass) =>
          exception_matches_handler(err, klass, state.val.builtins)
        Value::Tuple(items) => {
          for item in items {
            match item {
              Value::Class(klass) =>
                match
                  exception_matches_handler(err, klass, state.val.builtins) {
                  Ok(true) => return Ok(true)
                  Ok(false) => ()
                  Err(e) => return Err(e)
                }
              _ => ()
            }
          }
          Ok(false)
        }
        _ => Ok(false)
      }
    }
  }
}

///|
fn generator_dispatch_error(
  state : Ref[GeneratorState],
  err : RuntimeError,
) -> Result[Bool, RuntimeError] {
  let current = err
  while true {
    // Find the innermost cleanup frame (`with` exit or `try`).
    let mut cleanup_index : Int32? = None
    let mut i = state.val.frames.length()
    while i > 0 {
      i = i - 1
      match state.val.frames[i] {
        GenFrame::WithExit(..) | GenFrame::Try(..) => {
          cleanup_index = Some(i)
          break
        }
        _ => ()
      }
    }
    match cleanup_index {
      None => return Ok(false)
      Some(idx) => {
        // Unwind everything above the cleanup frame.
        while state.val.frames.length() > idx + 1 {
          let _ = state.val.frames.pop()
        }
        match state.val.frames[idx] {
          GenFrame::WithExit(exit~) => {
            // Ensure the exit hook only runs once.
            let _ = state.val.frames.pop()
            let exc_value = exception_value_from_runtime_error(
              current,
              state.val.globals,
              state.val.builtins,
              state.val.io,
            )
            let exc_type_value = match exc_value {
              Value::Instance(inst) => Value::Class(inst.class)
              _ => Value::Str(current.exc_type)
            }
            let tb_value = make_traceback_value(
              current.traceback,
              Value::Dict([]),
              current.span,
            )
            let exit_result = match
              call_callable_with_env(
                exit,
                [exc_type_value, exc_value, tb_value],
                [],
                state.val.globals,
                state.val.builtins,
                state.val.io,
              ) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            let suppress = match
              truthy_from_value_with_env(
                exit_result,
                state.val.globals,
                state.val.builtins,
                state.val.io,
              ) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            if suppress {
              // Exception suppressed.
              return Ok(true)
            } else {
              // Continue unwinding outward with the same exception.
              continue
            }
          }
          GenFrame::Try(
            handlers~,
            else_body~,
            finally_body~,
            phase~,
            pending~,
            handler_active~,
            loop_depth~
          ) => {
            let _ = else_body
            let _ = pending
            // If we're leaving an except handler, clear the active exception.
            if handler_active && active_exception_stack.val.length() > 0 {
              let _ = active_exception_stack.val.pop()
            }

            // `phase` tracks which block is currently active: 0=body, 1=else,
            // 2=handler, 3=finally.
            if phase != 2 && phase != 3 {
              let mut matched : ExceptHandler? = None
              for h in handlers {
                let ok = match
                  generator_try_handler_matches(current, h, state) {
                  Ok(v) => v
                  Err(e) => return Err(e)
                }
                if ok {
                  matched = Some(h)
                  break
                }
              }
              match matched {
                Some(h) => {
                  match h.name {
                    Some(name) =>
                      set_scoped_value(
                        state.val.locals,
                        state.val.globals,
                        name,
                        exception_value_from_runtime_error(
                          current,
                          state.val.globals,
                          state.val.builtins,
                          state.val.io,
                        ),
                      )
                    None => ()
                  }
                  active_exception_stack.val.push(current)
                  state.val.frames[idx] = GenFrame::Try(
                    handlers~,
                    else_body~,
                    finally_body~,
                    phase=2,
                    pending=None,
                    handler_active=true,
                    loop_depth~,
                  )
                  state.val.frames.push(
                    GenFrame::Block(body=h.body, index=0, loop_depth~),
                  )
                  return Ok(true)
                }
                None => ()
              }
            }
            if finally_body.length() > 0 {
              state.val.frames[idx] = GenFrame::Try(
                handlers~,
                else_body~,
                finally_body~,
                phase=3,
                pending=Some(current),
                handler_active=false,
                loop_depth~,
              )
              state.val.frames.push(
                GenFrame::Block(body=finally_body, index=0, loop_depth~),
              )
              return Ok(true)
            } else {
              // No finally: drop the try frame and keep searching outward.
              let _ = state.val.frames.pop()
              continue
            }
          }
          _ => return Ok(false)
        }
      }
    }
  }
  Ok(false)
}

///|
fn generator_resume_inner(
  state : Ref[GeneratorState],
  sent_value_in : Value,
  thrown_in : RuntimeError?,
) -> Result[Value, RuntimeError] {
  if state.val.done {
    return Err(generator_stop_iteration())
  }
  if !state.val.started {
    match sent_value_in {
      Value::None => ()
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "can't send non-None value to a just-started generator".to_string(),
          ),
        )
    }
    state.val.started = true
  }
  let mut sent_value = sent_value_in
  let mut thrown = thrown_in
  // Resume points only care about the sent value when the previous suspension
  // left a marker on the frame stack (yield in assignment, or yield from).
  // For plain `yield` statements, the sent value is ignored.
  if state.val.frames.length() == 0 {
    sent_value = Value::None
  } else {
    let top = state.val.frames[state.val.frames.length() - 1]
    match top {
      GenFrame::AfterAssign(..) | GenFrame::YieldFrom(..) => ()
      _ => sent_value = Value::None
    }
  }
  while true {
    if state.val.frames.length() == 0 {
      state.val.done = true
      return Err(generator_stop_iteration_with_value(Value::None))
    }
    let top_index = state.val.frames.length() - 1
    match state.val.frames[top_index] {
      GenFrame::AfterYield => {
        let _ = state.val.frames.pop()
        match thrown {
          Some(err) => {
            thrown = None
            let handled = match generator_dispatch_error(state, err) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            if handled {
              continue
            }
            state.val.done = true
            return Err(err)
          }
          None => continue
        }
      }
      GenFrame::AfterAssign(target~, value~) => {
        let _ = value
        let value_to_assign = sent_value
        sent_value = Value::None
        let _ = match
          assign_target_with_env(
            target,
            value_to_assign,
            state.val.locals,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(v) => v
          Err(err) => {
            let handled = match generator_dispatch_error(state, err) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            if handled {
              continue
            }
            state.val.done = true
            return Err(err)
          }
        }
        let _ = state.val.frames.pop()
        continue
      }
      GenFrame::YieldFrom(iterator~) =>
        match
          iterator_send(
            iterator,
            sent_value,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(value) => return Ok(value)
          Err(err) =>
            if is_stop_iteration(err) {
              let _ = state.val.frames.pop()
              let stop_value = match err.exc_value {
                Some(value) => value
                None => Value::None
              }
              if state.val.frames.length() > 0 {
                let next_top = state.val.frames[state.val.frames.length() - 1]
                match next_top {
                  GenFrame::AfterAssign(..) => sent_value = stop_value
                  _ => sent_value = Value::None
                }
              } else {
                sent_value = Value::None
              }
              continue
            } else {
              let handled = match generator_dispatch_error(state, err) {
                Ok(v) => v
                Err(e) => return Err(e)
              }
              if handled {
                continue
              }
              state.val.done = true
              return Err(err)
            }
        }
      GenFrame::Block(body~, index~, loop_depth~) => {
        if index >= body.length() {
          let _ = state.val.frames.pop()
          continue
        }
        state.val.frames[top_index] = GenFrame::Block(
          body~,
          index=index + 1,
          loop_depth~,
        )
        let stmt = body[index]
        match generator_step_statement(stmt, state, loop_depth) {
          Ok(Some(yielded)) => return Ok(yielded)
          Ok(None) => continue
          Err(err) => {
            let handled = match generator_dispatch_error(state, err) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            if handled {
              continue
            }
            if is_stop_iteration(err) {
              state.val.done = true
              state.val.frames = []
            }
            return Err(err)
          }
        }
      }
      GenFrame::For(
        target~,
        iterator~,
        body~,
        else_body~,
        broke~,
        done~,
        loop_depth~
      ) => {
        if done {
          let _ = state.val.frames.pop()
          continue
        }
        match
          iterator_next(
            iterator,
            None,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(item) => {
            let _ = match
              assign_target_with_env(
                target,
                item,
                state.val.locals,
                state.val.globals,
                state.val.builtins,
                state.val.io,
              ) {
              Ok(v) => v
              Err(err) => {
                let handled = match generator_dispatch_error(state, err) {
                  Ok(v) => v
                  Err(e) => return Err(e)
                }
                if handled {
                  continue
                }
                state.val.done = true
                return Err(err)
              }
            }
            state.val.frames.push(
              GenFrame::Block(body~, index=0, loop_depth=loop_depth + 1),
            )
            continue
          }
          Err(err) =>
            if is_stop_iteration(err) {
              state.val.frames[top_index] = GenFrame::For(
                target~,
                iterator~,
                body~,
                else_body~,
                broke~,
                done=true,
                loop_depth~,
              )
              if !broke && else_body.length() > 0 {
                state.val.frames.push(
                  GenFrame::Block(body=else_body, index=0, loop_depth~),
                )
              }
              continue
            } else {
              let handled = match generator_dispatch_error(state, err) {
                Ok(v) => v
                Err(e) => return Err(e)
              }
              if handled {
                continue
              }
              state.val.done = true
              return Err(err)
            }
        }
      }
      GenFrame::While(condition~, body~, else_body~, broke~, done~, loop_depth~) => {
        if done {
          let _ = state.val.frames.pop()
          continue
        }
        let cond_value = match
          eval_expr_with_env(
            condition,
            state.val.locals,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(value) => value
          Err(err) => {
            let handled = match generator_dispatch_error(state, err) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            if handled {
              continue
            }
            return Err(err)
          }
        }
        let ok = match
          truthy_from_value_with_env(
            cond_value,
            state.val.globals,
            state.val.builtins,
            state.val.io,
          ) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if ok {
          state.val.frames.push(
            GenFrame::Block(body~, index=0, loop_depth=loop_depth + 1),
          )
          continue
        }
        state.val.frames[top_index] = GenFrame::While(
          condition~,
          body~,
          else_body~,
          broke~,
          done=true,
          loop_depth~,
        )
        if !broke && else_body.length() > 0 {
          state.val.frames.push(
            GenFrame::Block(body=else_body, index=0, loop_depth~),
          )
        }
        continue
      }
      GenFrame::WithExit(exit~) => {
        let _ = state.val.frames.pop()
        let call_result = call_callable_with_env(
          exit,
          [Value::None, Value::None, Value::None],
          [],
          state.val.globals,
          state.val.builtins,
          state.val.io,
        )
        match call_result {
          Ok(_) => continue
          Err(err) => {
            let handled = match generator_dispatch_error(state, err) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            if handled {
              continue
            }
            state.val.done = true
            return Err(err)
          }
        }
      }
      GenFrame::Try(
        handlers~,
        else_body~,
        finally_body~,
        phase~,
        pending~,
        handler_active~,
        loop_depth~
      ) => {
        let _ = handlers
        let _ = loop_depth
        // Phase: 0=body done, 1=else done, 2=handler done, 3=finally done.
        if phase == 0 {
          if else_body.length() > 0 {
            state.val.frames[top_index] = GenFrame::Try(
              handlers~,
              else_body~,
              finally_body~,
              phase=1,
              pending~,
              handler_active~,
              loop_depth~,
            )
            state.val.frames.push(
              GenFrame::Block(body=else_body, index=0, loop_depth~),
            )
            continue
          }
          if finally_body.length() > 0 {
            state.val.frames[top_index] = GenFrame::Try(
              handlers~,
              else_body~,
              finally_body~,
              phase=3,
              pending~,
              handler_active~,
              loop_depth~,
            )
            state.val.frames.push(
              GenFrame::Block(body=finally_body, index=0, loop_depth~),
            )
            continue
          }
          let _ = state.val.frames.pop()
          continue
        }
        if phase == 1 || phase == 2 {
          if handler_active && active_exception_stack.val.length() > 0 {
            let _ = active_exception_stack.val.pop()
          }
          if finally_body.length() > 0 {
            state.val.frames[top_index] = GenFrame::Try(
              handlers~,
              else_body~,
              finally_body~,
              phase=3,
              pending~,
              handler_active=false,
              loop_depth~,
            )
            state.val.frames.push(
              GenFrame::Block(body=finally_body, index=0, loop_depth~),
            )
            continue
          }
          let _ = state.val.frames.pop()
          continue
        }
        if phase == 3 {
          let pending_err = pending
          let _ = state.val.frames.pop()
          match pending_err {
            Some(err) => {
              let handled = match generator_dispatch_error(state, err) {
                Ok(v) => v
                Err(e) => return Err(e)
              }
              if handled {
                continue
              }
              state.val.done = true
              return Err(err)
            }
            None => continue
          }
        }
        // Unknown phase: drop the frame.
        let _ = state.val.frames.pop()
        continue
      }
    }
  }
  Err(generator_stop_iteration())
}

///|
fn generator_resume(
  state : Ref[GeneratorState],
  sent_value_in : Value,
  thrown_in : RuntimeError?,
) -> Result[Value, RuntimeError] {
  let _ = match ensure_recursion_available() {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  push_traceback_frame(state.val.name, state.val.filename)
  push_closure_env(state.val.closure)
  push_scope_decls_with_nonlocals(
    state.val.global_names,
    state.val.nonlocal_names,
  )
  if state.val.is_async {
    coroutine_enter_context()
  }
  let result = generator_resume_inner(state, sent_value_in, thrown_in)
  if state.val.is_async {
    coroutine_leave_context()
  }
  pop_scope_decls()
  pop_closure_env()
  pop_traceback_frame()
  result
}

///|
fn generator_next(
  generator_value : Value,
  default_value : Value?,
) -> Result[Value, RuntimeError] {
  let state = match generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match generator_resume(state, Value::None, None) {
    Ok(value) => Ok(value)
    Err(err) =>
      if is_stop_iteration(err) {
        match default_value {
          Some(v) => Ok(v)
          None => Err(err)
        }
      } else {
        Err(err)
      }
  }
}

///|
fn generator_send(
  generator_value : Value,
  value : Value,
) -> Result[Value, RuntimeError] {
  let state = match generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  generator_resume(state, value, None)
}

///|
fn generator_close(generator_value : Value) -> Result[Value, RuntimeError] {
  let state = match generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  state.val.done = true
  state.val.frames = []
  Ok(Value::None)
}

///|
fn generator_throw(
  generator_value : Value,
  exc : Value,
) -> Result[Value, RuntimeError] {
  let state = match generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let err = match exc {
    Value::Instance(inst) => runtime_error_from_exception_instance(inst)
    Value::Class(klass) =>
      make_runtime_error(RuntimeErrorKind::Runtime, klass.name)
    Value::Str(text) => make_runtime_error(RuntimeErrorKind::Runtime, text)
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "exceptions must derive from BaseException".to_string(),
        ),
      )
  }
  generator_resume(state, Value::None, Some(err))
}

///|
fn async_generator_stop_iteration() -> RuntimeError {
  make_runtime_error(
    RuntimeErrorKind::Runtime,
    "StopAsyncIteration".to_string(),
  )
}

///|
fn async_generator_next(generator_value : Value) -> Result[Value, RuntimeError] {
  let state = match async_generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match generator_resume(state, Value::None, None) {
    Ok(value) => Ok(value)
    Err(err) =>
      if is_stop_iteration(err) {
        Err(async_generator_stop_iteration())
      } else {
        Err(err)
      }
  }
}

///|
fn async_generator_send(
  generator_value : Value,
  value : Value,
) -> Result[Value, RuntimeError] {
  let state = match async_generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match generator_resume(state, value, None) {
    Ok(value) => Ok(value)
    Err(err) =>
      if is_stop_iteration(err) {
        Err(async_generator_stop_iteration())
      } else {
        Err(err)
      }
  }
}

///|
fn async_generator_close(
  generator_value : Value,
) -> Result[Value, RuntimeError] {
  let state = match async_generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  state.val.done = true
  state.val.frames = []
  Ok(Value::None)
}

///|
fn async_generator_throw(
  generator_value : Value,
  exc : Value,
) -> Result[Value, RuntimeError] {
  let state = match async_generator_state_from_value(generator_value) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let err = match exc {
    Value::Instance(inst) => runtime_error_from_exception_instance(inst)
    Value::Class(klass) =>
      make_runtime_error(RuntimeErrorKind::Runtime, klass.name)
    Value::Str(text) => make_runtime_error(RuntimeErrorKind::Runtime, text)
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "exceptions must derive from BaseException".to_string(),
        ),
      )
  }
  generator_resume(state, Value::None, Some(err))
}

///|
fn async_generator_make_anext_coroutine(
  generator_value : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Value {
  let body : Array[Stmt] = [
    Stmt::Return(
      Some(
        Expr::Call(callee=Expr::Name("__mpython_asyncgen_next"), args=[
          Expr::Name("self"),
        ]),
      ),
    ),
  ]
  let locals : Array[(String, Value)] = [("self", generator_value)]
  let filename = current_traceback_filename()
  coroutine_new(
    body,
    locals,
    globals,
    builtins,
    io,
    [],
    [],
    [],
    "".to_string(),
    filename,
    Config::default(),
  )
}

///|
fn async_generator_make_asend_coroutine(
  generator_value : Value,
  send_value : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Value {
  let body : Array[Stmt] = [
    Stmt::Return(
      Some(
        Expr::Call(callee=Expr::Name("__mpython_asyncgen_send"), args=[
          Expr::Name("self"),
          Expr::Name("value"),
        ]),
      ),
    ),
  ]
  let locals : Array[(String, Value)] = [
    ("self", generator_value),
    ("value", send_value),
  ]
  let filename = current_traceback_filename()
  coroutine_new(
    body,
    locals,
    globals,
    builtins,
    io,
    [],
    [],
    [],
    "".to_string(),
    filename,
    Config::default(),
  )
}

///|
fn async_generator_make_athrow_coroutine(
  generator_value : Value,
  exc : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Value {
  let body : Array[Stmt] = [
    Stmt::Return(
      Some(
        Expr::Call(callee=Expr::Name("__mpython_asyncgen_throw"), args=[
          Expr::Name("self"),
          Expr::Name("exc"),
        ]),
      ),
    ),
  ]
  let locals : Array[(String, Value)] = [
    ("self", generator_value),
    ("exc", exc),
  ]
  let filename = current_traceback_filename()
  coroutine_new(
    body,
    locals,
    globals,
    builtins,
    io,
    [],
    [],
    [],
    "".to_string(),
    filename,
    Config::default(),
  )
}

///|
fn async_generator_make_aclose_coroutine(
  generator_value : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Value {
  let body : Array[Stmt] = [
    Stmt::Return(
      Some(
        Expr::Call(callee=Expr::Name("__mpython_asyncgen_close"), args=[
          Expr::Name("self"),
        ]),
      ),
    ),
  ]
  let locals : Array[(String, Value)] = [("self", generator_value)]
  let filename = current_traceback_filename()
  coroutine_new(
    body,
    locals,
    globals,
    builtins,
    io,
    [],
    [],
    [],
    "".to_string(),
    filename,
    Config::default(),
  )
}