///|
fn Plan::update(
  self : Plan,
  path : Int32,
  body : Int32,
  mode : UpdateMode,
  input : Value,
  env : Env,
  span : @ast.Span?,
) -> Machine {
  fn paths() -> Machine {
    self.machine(path, { json: input.json, path: Some([]), }, env, true)
  }
  match mode {
    Modify => self.modify(paths(), body, input.json, env, span)
    Assign =>
      self
      .machine(body, input, env, false)
      .flat_map(rhs => {
        let updated = Ref(input.json)
        paths()
        .map(v => {
          let p = path_result(v, span)
          guard p.json is Array(segments) else { return v }
          updated.val = set_at_path(updated.val, segments, rhs.json) catch {
            err => raise primitive_error(err, span)
          }
          v
        })
        .collect(_ => Machine::one(value(updated.val)))
      })
  }
}

///|
fn Plan::modify(
  self : Plan,
  paths : Machine,
  body : Int32,
  original : Json,
  env : Env,
  span : @ast.Span?,
) -> Machine {
  let updated = Ref(original)
  let deleted : Array[Array[Json]] = []
  paths
  .flat_map(entry => {
    let p = path_result(entry, span)
    guard p.json is Array(segments) else { return Machine::empty() }
    let current = get_at_path(updated.val, segments).unwrap_or(null)
    let changes = self.machine(body, value(current), env, false).take(1)
    changes.collect(vs => {
      match vs.get(0) {
        Some(v) =>
          updated.val = set_at_path(updated.val, segments, v.json) catch {
            err => raise primitive_error(err, span)
          }
        None => deleted.push(segments)
      }
      Machine::empty()
    })
  })
  .collect(_ => {
    let result = delete_paths(updated.val, deleted.map(p => p)) catch {
      err => raise primitive_error(err, span)
    }
    Machine::one(value(result))
  })
}