///|
fn learning_result(
  experiment : Experiment,
  model : Machine?,
  status : LearningStatus,
  rounds : Array[LearningRound],
  counterexamples : Array[Difference],
) -> LearningResult {
  {
    model,
    status,
    statistics: experiment.statistics(),
    rounds,
    counterexamples,
  }
}

///|
/// Synchronous L* compatibility entry. The same suspended learner also powers
/// native asynchronous runners.
pub fn learn(
  experiment : Experiment,
  oracle : Oracle,
  max_states~ : Int32 = 256,
  max_rounds~ : Int32 = 64,
  max_table_cells~ : Int32 = 1000000,
  max_table_symbols~ : Int32 = 4000000,
  max_prefixes~ : Int32 = 20000,
) -> LearningResult raise {
  learn_with(
    experiment,
    oracle,
    LStar,
    max_states~,
    max_rounds~,
    max_table_cells~,
    max_table_symbols~,
    max_prefixes~,
  )
}

///|
/// Select L* or KV without changing the synchronous custom-oracle contract.
pub fn learn_with(
  experiment : Experiment,
  oracle : Oracle,
  algorithm : Algorithm,
  max_states~ : Int32 = 256,
  max_rounds~ : Int32 = 64,
  max_table_cells~ : Int32 = 1000000,
  max_table_symbols~ : Int32 = 4000000,
  max_prefixes~ : Int32 = 20000,
) -> LearningResult raise {
  let mut driver : LearningDriver? = None
  fn result(status) {
    match driver {
      Some(driver) => driver.result(experiment.statistics(), status)
      None => learning_result(experiment, None, status, [], [])
    }
  }
  try {
    let active = LearningDriver::new(
      algorithm,
      experiment.alphabet,
      max_states~,
      max_rounds~,
      max_table_cells~,
      max_table_symbols~,
      max_prefixes~,
    )
    driver = Some(active)
    for ;; {
      match active.event(queries=experiment.statistics().queries) {
        Query(request) =>
          active.submit(request.id, experiment.query(request.inputs))
        Hypothesis(model) =>
          match (oracle.check)(model, experiment) {
            Counterexample(word) =>
              active.refine(word, experiment.query(word, bypass_cache=true))
            NoCounterexample(evidence) => return result(Supported(evidence))
            Inconclusive(reason) => return result(Inconclusive(reason))
          }
      }
    }
  } catch {
    LimitExceeded(reason) => result(Inconclusive(reason))
    InconsistentObservation(word~, expected~, actual~) =>
      result(Unstable(word~, expected~, actual~))
    error => raise error
  }
}