///|
priv struct Target {
  shape : @smithy.ShapeId
  adapter : @contract.AdapterId?
}

///|
priv enum Build {
  BList(@validation.InstancePath, Int32)
  BStruct(@validation.InstancePath, Array[(String, Bool)])
  BUnion(@validation.InstancePath, String)
  BMap(@validation.InstancePath, Array[String])
  BObject(@validation.InstancePath, Array[String])
}

///|
priv enum Task {
  Visit(Json, @validation.InstancePath, Target, Int32)
  Document(Json, @validation.InstancePath, Int32)
  Rest(Array[Json], Int32, @validation.InstancePath, Target, Int32)
  /// A null element or value of a sparse list or map, kept without reading its target layout.
  SparseNull(@validation.InstancePath)
  Assemble(Build)
}

///|
priv struct Reader {
  contract : @contract.WireContract
  adapters : AdapterRegistry
  request : Request
  tasks : Array[Task]
  results : Array[LNode]
}

///|
fn Reader::invalid(self : Reader, path : @validation.InstancePath) -> Unit {
  self.results.push({ kind: LInvalid, source: path, })
}

///|
fn Reader::fail(
  self : Reader,
  code : CodecCode,
  path : @validation.InstancePath,
  shape : @smithy.ShapeId,
  message : String,
) -> Unit {
  self.request.emit(code, path, shape, message)
  self.invalid(path)
}

///|
fn Reader::shape(
  self : Reader,
  id : @smithy.ShapeId,
) -> @contract.ShapeContract {
  self.contract.shapes.get(id).unwrap()
}

///|
fn Reader::room(
  self : Reader,
  path : @validation.InstancePath,
  shape : @smithy.ShapeId,
  children : Int32,
) -> Bool {
  let meter = self.request.meter
  if children > meter.limit() - meter.used() {
    self.request.stop(path, shape, "work")
    return false
  }
  self.request.complete
}

///|
/// Call an adapter. Findings are emitted only when `quiet` is false.
fn Reader::adapt(
  self : Reader,
  id : @contract.AdapterId,
  value : Json,
  path : @validation.InstancePath,
  shape : @smithy.ShapeId,
  quiet~ : Bool = false,
) -> ScalarValue? {
  guard self.adapters.get(id) is Some(adapter) else {
    if !quiet {
      self.request.emit(
        AdapterFailure,
        path,
        shape,
        "I expected the registered adapter \{id}.",
      )
    }
    return None
  }
  let kind = @contract.JsonKind::of(value)
  if !adapter.signature.inputs.contains(kind) {
    if !quiet {
      self.request.emit(
        KindMismatch,
        path,
        shape,
        "I expected a value that adapter \{id} reads, but found \{kind}.",
      )
    }
    return None
  }
  if !self.request.charge(path, shape) {
    return None
  }
  let result = (adapter.reader)(value, { meter: self.request.meter, })
  if self.request.meter.exhausted() {
    self.request.stop(path, shape, "work")
    return None
  }
  match result {
    Ok(scalar) => {
      if scalar.kind() != adapter.signature.output {
        if !quiet {
          self.request.emit(
            AdapterFailure,
            path,
            shape,
            "Adapter \{id} produced a \{scalar.kind()} value instead of \{adapter.signature.output}.",
          )
        }
        return None
      }
      if scalar is Text(text) &&
        !self.request.charge(path, shape, units=text.length() / 64) {
        return None
      }
      Some(scalar)
    }
    Err(failure) => {
      if !quiet {
        self.request.emit(
          AdapterFailure,
          path.append(failure.path),
          shape,
          failure.message,
        )
      }
      None
    }
  }
}

///|
fn scalar_node(scalar : ScalarValue) -> LKind {
  match scalar {
    Text(s) => LString(s)
    Numeric(n) => LNumber(n)
    Flag(b) => LBool(b)
  }
}

///|
fn expected(kind : @contract.JsonKind, found : Json) -> String {
  let article = if kind is (ArrayKind | ObjectKind) { "an" } else { "a" }
  "I expected \{article} \{kind} value, but found \{@contract.JsonKind::of(found)}."
}

///|
fn Reader::visit(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  target : Target,
  depth : Int32,
) -> Unit {
  if !self.request.charge(path, target.shape) {
    return
  }
  if depth > self.request.limits.depth {
    self.request.stop(path, target.shape, "depth")
    return
  }
  let shape = self.shape(target.shape)
  match shape.layout {
    Scalar(adapter) => {
      let adapter = match target.adapter {
        Some(a) => Some(a)
        None => adapter
      }
      match adapter {
        Some(id) =>
          match self.adapt(id, json, path, shape.id) {
            Some(scalar) =>
              self.results.push({ kind: scalar_node(scalar), source: path, })
            None => self.invalid(path)
          }
        None =>
          if shape.kind == SimpleKind(Document) {
            self.tasks.push(Document(json, path, depth))
          } else {
            match json {
              Null => self.results.push({ kind: LNull, source: path, })
              True => self.results.push({ kind: LBool(true), source: path, })
              False => self.results.push({ kind: LBool(false), source: path, })
              String(s) =>
                self.results.push({ kind: LString(s), source: path, })
              Number(_, ..) =>
                match json_number(json) {
                  Some(n) =>
                    self.results.push({ kind: LNumber(n), source: path, })
                  None =>
                    self.fail(
                      Constraint(InvalidNumber),
                      path,
                      shape.id,
                      "I expected an exact finite numeric token.",
                    )
                }
              Array(_) | Object(_) =>
                self.fail(
                  KindMismatch,
                  path,
                  shape.id,
                  "I expected a scalar value, but found \{@contract.JsonKind::of(json)}.",
                )
            }
          }
      }
    }
    ListShape => {
      guard json is Array(items) else {
        if json is Null {
          self.results.push({ kind: LNull, source: path, })
          return
        }
        self.fail(KindMismatch, path, shape.id, expected(ArrayKind, json))
        return
      }
      if !self.room(path, shape.id, items.length()) {
        return
      }
      let item = shape.members[0]
      self.tasks.push(Assemble(BList(path, items.length())))
      for i = items.length() - 1; i >= 0; i = i - 1 {
        if shape.sparse && items[i] is Null {
          self.tasks.push(SparseNull(index_path(path, i)))
          continue
        }
        self.tasks.push(
          Visit(
            items[i],
            index_path(path, i),
            { shape: item.target, adapter: item.adapter, },
            depth + 1,
          ),
        )
      }
    }
    Dictionary(layout) => self.dictionary(json, path, shape, layout, depth)
    ObjectStructure | TupleStructure(_) => {
      if self.apply_forms(json, path, shape, depth) {
        return
      }
      self.structure(json, path, shape, depth)
    }
    UnionShape(layout) => {
      if self.apply_forms(json, path, shape, depth) {
        return
      }
      self.union_(json, path, shape, layout, depth)
    }
  }
}

///|
fn Reader::structure(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  shape : @contract.ShapeContract,
  depth : Int32,
) -> Unit {
  match shape.layout {
    TupleStructure(t) => {
      guard json is Array(items) else {
        if json is Null {
          self.results.push({ kind: LNull, source: path, })
          return
        }
        self.fail(KindMismatch, path, shape.id, expected(ArrayKind, json))
        return
      }
      self.positions(items, 0, path, shape, t.fixed, t.rest, t.optional, depth)
    }
    _ => {
      guard json is Object(fields) else {
        if json is Null {
          self.results.push({ kind: LNull, source: path, })
          return
        }
        self.fail(KindMismatch, path, shape.id, expected(ObjectKind, json))
        return
      }
      self.object_fields(fields, path, shape, None, depth)
    }
  }
}

///|
/// Project the fields of an object onto structure members, skipping a discriminator.
fn Reader::object_fields(
  self : Reader,
  fields : Map[String, Json],
  path : @validation.InstancePath,
  shape : @contract.ShapeContract,
  skip : @contract.WireName?,
  depth : Int32,
) -> Unit {
  if !self.room(path, shape.id, fields.length()) {
    return
  }
  let slots : Array[(Json, @validation.InstancePath, String)?] = Array::make(
    shape.members.length(),
    None,
  )
  let mut failed = false
  let flattened = shape.members.search_by(m => m.flatten)
  let rest : Map[String, Json] = Map([])
  for name, value in fields {
    if skip is Some(tag) && tag.to_string() == name {
      continue
    }
    let field = field_path(path, name)
    let mut found = None
    for i, m in shape.members {
      if (!m.flatten && m.wire.to_string() == name) ||
        m.aliases.iter().any(a => a.to_string() == name) {
        found = Some(i)
        break
      }
    }
    match found {
      None if flattened is Some(_) => rest[name] = value
      None =>
        if self.contract.unknown_members == RejectUnknown {
          self.request.emit(
            UnknownMember,
            field,
            shape.id,
            "I expected a declared member, but found unknown member \{name.escape()}.",
          )
          failed = true
        }
      Some(i) if value is Null && shape.members[i].null_when_absent => ()
      Some(i) =>
        match slots[i] {
          Some((_, _, first)) => {
            self.request.emit(
              MemberCollision,
              field,
              shape.members[i].member_id,
              "Fields \{first.escape()} and \{name.escape()} both supply member \{shape.members[i].name}.",
            )
            failed = true
          }
          None => slots[i] = Some((value, field, name))
        }
    }
  }
  if flattened is Some(f) && !rest.is_empty() {
    match slots[f] {
      Some((_, _, field_name)) => {
        self.request.emit(
          MemberCollision,
          path,
          shape.members[f].member_id,
          "Field \{field_name.escape()} and flattened fields both supply member \{shape.members[f].name}.",
        )
        failed = true
      }
      None => slots[f] = Some((Json::object(rest), path, shape.members[f].name))
    }
  }
  for i, m in shape.members {
    if m.required && slots[i] is None {
      self.request.emit(
        MissingMember,
        field_path(path, m.wire.to_string()),
        m.member_id,
        "I expected the required member \{m.name}, but found it absent.",
      )
      failed = true
    }
  }
  if failed {
    self.invalid(path)
    return
  }
  let names : Array[(String, Bool)] = []
  let visits : Array[Task] = []
  for i, m in shape.members {
    if slots[i] is Some((value, field, _)) {
      names.push((m.name, m.empty_as_absent))
      visits.push(
        Visit(value, field, { shape: m.target, adapter: m.adapter, }, depth + 1),
      )
    }
  }
  self.tasks.push(Assemble(BStruct(path, names)))
  for i = visits.length() - 1; i >= 0; i = i - 1 {
    self.tasks.push(visits[i])
  }
}

///|
/// Project array positions onto structure members. `offset` is the index of the first position in the
/// source array, which is 1 after a tag.
fn Reader::positions(
  self : Reader,
  items : ArrayView[Json],
  offset : Int32,
  path : @validation.InstancePath,
  shape : @contract.ShapeContract,
  fixed : Array[Int32],
  rest : Int32?,
  optional : @contract.OptionalTail?,
  depth : Int32,
) -> Unit {
  if !self.room(path, shape.id, items.length()) {
    return
  }
  let slots : Array[Task?] = Array::make(shape.members.length(), None)
  let mut failed = false
  for p, i in fixed {
    let m = shape.members[i]
    let at = index_path(path, p + offset)
    if p >= items.length() {
      if m.required || optional is None {
        self.request.emit(
          MissingMember,
          at,
          m.member_id,
          "I expected member \{m.name} at position \{p + offset}, but the array has \{items.length() + offset} elements.",
        )
        failed = true
      }
      continue
    }
    let item = items[p]
    if item is Null && !m.required && optional == Some(NullTail) {
      continue
    }
    slots[i] = Some(
      Visit(item, at, { shape: m.target, adapter: m.adapter, }, depth + 1),
    )
  }
  if items.length() > fixed.length() {
    match rest {
      None => {
        self.request.emit(
          Arity,
          index_path(path, fixed.length() + offset),
          shape.id,
          "I expected at most \{fixed.length() + offset} elements, but found \{items.length() + offset}.",
        )
        failed = true
      }
      Some(r) => {
        let m = shape.members[r]
        let list = self.shape(m.target)
        let item = list.members[0]
        slots[r] = Some(
          Rest(
            items[fixed.length():].to_owned(),
            fixed.length() + offset,
            path,
            { shape: item.target, adapter: item.adapter, },
            depth + 1,
          ),
        )
      }
    }
  } else if rest is Some(r) && shape.members[r].required {
    slots[r] = Some(
      Rest(
        [],
        fixed.length() + offset,
        path,
        { shape: shape.members[r].target, adapter: None, },
        depth + 1,
      ),
    )
  }
  if failed {
    self.invalid(path)
    return
  }
  let names : Array[(String, Bool)] = []
  let visits : Array[Task] = []
  for i, m in shape.members {
    if slots[i] is Some(task) {
      names.push((m.name, m.empty_as_absent))
      visits.push(task)
    }
  }
  self.tasks.push(Assemble(BStruct(path, names)))
  for i = visits.length() - 1; i >= 0; i = i - 1 {
    self.tasks.push(visits[i])
  }
}

///|
fn Reader::dictionary(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  shape : @contract.ShapeContract,
  layout : @contract.DictionaryLayout,
  depth : Int32,
) -> Unit {
  let value_member = shape.members[1]
  let value_target = {
    shape: value_member.target,
    adapter: value_member.adapter,
  }
  let keys : Array[String] = []
  let visits : Array[Task] = []
  let seen : Map[String, @validation.InstancePath] = Map([])
  let mut failed = false
  let add = fn(key : String, key_path, value : Json, value_path) {
    if seen.contains(key) {
      self.request.emit(
        DuplicateKey,
        key_path,
        shape.id,
        "I found a second entry for the key \{key.escape()}.",
      )
      failed = true
      return
    }
    seen[key] = key_path
    keys.push(key)
    if shape.sparse && value is Null {
      visits.push(SparseNull(value_path))
    } else {
      visits.push(Visit(value, value_path, value_target, depth + 1))
    }
  }
  let read_key = fn(key : Json, key_path) -> String? {
    match layout.key_adapter {
      Some(id) =>
        match self.adapt(id, key, key_path, shape.members[0].member_id) {
          Some(Text(text)) => Some(text)
          _ => None
        }
      None =>
        match key {
          String(text) => Some(text)
          _ => {
            self.request.emit(
              KindMismatch,
              key_path,
              shape.members[0].member_id,
              expected(StringKind, key),
            )
            None
          }
        }
    }
  }
  let entries_layout = if layout.accept_other {
    json is Array(_)
  } else {
    layout.entries
  }
  if entries_layout {
    guard json is Array(entries) else {
      self.fail(KindMismatch, path, shape.id, expected(ArrayKind, json))
      return
    }
    if !self.room(path, shape.id, entries.length()) {
      return
    }
    for i, entry in entries {
      let at = index_path(path, i)
      guard entry is Array(pair) else {
        self.request.emit(
          KindMismatch,
          at,
          shape.id,
          "I expected a [key, value] entry, but found \{@contract.JsonKind::of(entry)}.",
        )
        failed = true
        continue
      }
      guard pair.length() == 2 else {
        self.request.emit(
          Arity,
          at,
          shape.id,
          "I expected a [key, value] entry of 2 elements, but found \{pair.length()}.",
        )
        failed = true
        continue
      }
      match read_key(pair[0], index_path(at, 0)) {
        Some(key) => add(key, index_path(at, 0), pair[1], index_path(at, 1))
        None => failed = true
      }
    }
  } else {
    guard json is Object(fields) else {
      self.fail(KindMismatch, path, shape.id, expected(ObjectKind, json))
      return
    }
    if !self.room(path, shape.id, fields.length()) {
      return
    }
    for name, value in fields {
      let at = field_path(path, name)
      match read_key(Json::string(name), at) {
        Some(key) => add(key, at, value, at)
        None => failed = true
      }
    }
  }
  if failed {
    self.invalid(path)
    return
  }
  self.tasks.push(Assemble(BMap(path, keys)))
  for i = visits.length() - 1; i >= 0; i = i - 1 {
    self.tasks.push(visits[i])
  }
}

///|
fn Reader::member_by_tag(
  self : Reader,
  shape : @contract.ShapeContract,
  tag : String,
  path : @validation.InstancePath,
) -> Int32? {
  for i, m in shape.members {
    if m.tag.to_string() == tag ||
      m.aliases.iter().any(a => a.to_string() == tag) {
      return Some(i)
    }
  }
  self.request.emit(
    UnknownTag,
    path,
    shape.id,
    "I expected a declared union tag, but found \{tag.escape()}.",
  )
  None
}

///|
fn unit_like(shape : @contract.ShapeContract) -> Bool {
  shape.kind == StructureKind &&
  shape.members.is_empty() &&
  shape.layout is ObjectStructure
}

///|
fn Reader::union_(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  shape : @contract.ShapeContract,
  layout : @contract.UnionLayout,
  depth : Int32,
) -> Unit {
  let layout = if shape.accepts.is_empty() {
    layout
  } else {
    match self.accepted_layout(json, path, shape, layout) {
      Some(l) => l
      None => return
    }
  }
  match layout {
    External => {
      guard json is Object(fields) else {
        self.fail(KindMismatch, path, shape.id, expected(ObjectKind, json))
        return
      }
      guard fields.length() == 1 else {
        self.fail(
          Arity,
          path,
          shape.id,
          "I expected exactly one union member, but found \{fields.length()}.",
        )
        return
      }
      for tag, value in fields {
        let at = field_path(path, tag)
        guard self.member_by_tag(shape, tag, at) is Some(i) else {
          self.invalid(path)
          return
        }
        let m = shape.members[i]
        self.tasks.push(Assemble(BUnion(path, m.name)))
        self.tasks.push(
          Visit(value, at, { shape: m.target, adapter: m.adapter, }, depth + 1),
        )
      }
    }
    Internal(tag~) => {
      guard json is Object(fields) else {
        self.fail(KindMismatch, path, shape.id, expected(ObjectKind, json))
        return
      }
      let at = field_path(path, tag.to_string())
      guard fields.get(tag.to_string()) is Some(value) else {
        self.fail(
          MissingMember,
          at,
          shape.id,
          "I expected the discriminator \{tag}, but found it absent.",
        )
        return
      }
      guard value is String(name) else {
        self.fail(KindMismatch, at, shape.id, expected(StringKind, value))
        return
      }
      guard self.member_by_tag(shape, name, at) is Some(i) else {
        self.invalid(path)
        return
      }
      let m = shape.members[i]
      self.tasks.push(Assemble(BUnion(path, m.name)))
      self.object_fields(
        fields,
        path,
        self.shape(m.target),
        Some(tag),
        depth + 1,
      )
    }
    Adjacent(tag~, content~) => {
      guard json is Object(fields) else {
        self.fail(KindMismatch, path, shape.id, expected(ObjectKind, json))
        return
      }
      let mut failed = false
      for name, _ in fields {
        if name != tag.to_string() && name != content.to_string() {
          self.request.emit(
            UnknownMember,
            field_path(path, name),
            shape.id,
            "I expected only the fields \{tag} and \{content}, but found \{name.escape()}.",
          )
          failed = true
        }
      }
      let at = field_path(path, tag.to_string())
      guard fields.get(tag.to_string()) is Some(value) else {
        self.fail(
          MissingMember,
          at,
          shape.id,
          "I expected the tag field \{tag}, but found it absent.",
        )
        return
      }
      guard value is String(name) else {
        self.fail(KindMismatch, at, shape.id, expected(StringKind, value))
        return
      }
      guard !failed && self.member_by_tag(shape, name, at) is Some(i) else {
        self.invalid(path)
        return
      }
      let m = shape.members[i]
      let target = self.shape(m.target)
      let content_path = field_path(path, content.to_string())
      match fields.get(content.to_string()) {
        Some(payload) => {
          self.tasks.push(Assemble(BUnion(path, m.name)))
          self.tasks.push(
            Visit(
              payload,
              content_path,
              { shape: m.target, adapter: m.adapter, },
              depth + 1,
            ),
          )
        }
        None =>
          if unit_like(target) {
            self.results.push({
              kind: LUnion(m.name, { kind: LStruct([]), source: path, }),
              source: path,
            })
          } else {
            self.fail(
              MissingMember,
              content_path,
              shape.id,
              "I expected the content field \{content}, but found it absent.",
            )
          }
      }
    }
    TagArray(tag_only_unit~) => {
      guard json is Array(items) else {
        self.fail(KindMismatch, path, shape.id, expected(ArrayKind, json))
        return
      }
      guard items.length() >= 1 else {
        self.fail(Arity, path, shape.id, "I expected a tag at position 0.")
        return
      }
      let at = index_path(path, 0)
      guard items[0] is String(name) else {
        self.fail(KindMismatch, at, shape.id, expected(StringKind, items[0]))
        return
      }
      guard self.member_by_tag(shape, name, at) is Some(i) else {
        self.invalid(path)
        return
      }
      let m = shape.members[i]
      let target = self.shape(m.target)
      if tag_only_unit && unit_like(target) {
        guard items.length() == 1 else {
          self.fail(
            Arity,
            index_path(path, 1),
            shape.id,
            "I expected only the tag of the Unit member \{m.name}.",
          )
          return
        }
        self.results.push({
          kind: LUnion(m.name, { kind: LStruct([]), source: path, }),
          source: path,
        })
        return
      }
      match target.layout {
        TupleStructure(t) => {
          self.tasks.push(Assemble(BUnion(path, m.name)))
          self.positions(
            items[1:],
            1,
            path,
            target,
            t.fixed,
            t.rest,
            t.optional,
            depth + 1,
          )
        }
        _ => {
          guard items.length() == 2 else {
            self.fail(
              Arity,
              path,
              shape.id,
              "I expected a tag and one payload element, but found \{items.length()} elements.",
            )
            return
          }
          self.tasks.push(Assemble(BUnion(path, m.name)))
          self.tasks.push(
            Visit(
              items[1],
              index_path(path, 1),
              { shape: m.target, adapter: m.adapter, },
              depth + 1,
            ),
          )
        }
      }
    }
    Untagged(kinds) => {
      let kind = @contract.JsonKind::of(json)
      let mut selected = None
      for entry in kinds {
        if entry.0 == kind {
          selected = Some(entry.1)
        }
      }
      guard selected is Some(i) else {
        self.fail(
          KindMismatch,
          path,
          shape.id,
          "I expected a value of one of the union's JSON kinds, but found \{kind}.",
        )
        return
      }
      let m = shape.members[i]
      self.tasks.push(Assemble(BUnion(path, m.name)))
      self.tasks.push(
        Visit(json, path, { shape: m.target, adapter: m.adapter, }, depth + 1),
      )
    }
  }
}

///|
/// Read a compact form. Returns true when a form, or an ambiguity between forms, decided the value.
fn Reader::apply_forms(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  shape : @contract.ShapeContract,
  depth : Int32,
) -> Bool {
  if shape.forms.is_empty() {
    return false
  }
  let kind = @contract.JsonKind::of(json)
  let matches : Array[@contract.Form] = []
  for form in shape.forms {
    if form.reader.kind != kind {
      continue
    }
    if form.reader.field is Some(field) &&
      !(json is Object(fields) && fields.contains(field.to_string())) {
      continue
    }
    if form.reader.absent is Some(field) &&
      json is Object(fields) &&
      fields.contains(field.to_string()) {
      continue
    }
    if form.reader.adapter is Some(id) &&
      self.adapt(id, json, path, shape.id, quiet=true) is None {
      if !self.request.complete {
        return true
      }
      continue
    }
    matches.push(form)
  }
  match matches {
    [] => false
    [form] => {
      match form.union_member {
        Some(i) => {
          let m = shape.members[i]
          self.tasks.push(Assemble(BUnion(path, m.name)))
          self.bind(
            json,
            path,
            form.binding,
            self.shape(m.target),
            m.adapter,
            depth,
          )
        }
        None => self.bind(json, path, form.binding, shape, None, depth)
      }
      true
    }
    _ => {
      self.fail(
        AmbiguousForm,
        path,
        shape.id,
        "I found \{matches.length()} compact forms that read this \{kind} value.",
      )
      true
    }
  }
}

///|
fn Reader::bind(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  binding : @contract.FormBinding,
  payload : @contract.ShapeContract,
  adapter : @contract.AdapterId?,
  depth : Int32,
) -> Unit {
  match binding {
    Whole(-1) =>
      self.tasks.push(
        Visit(json, path, { shape: payload.id, adapter, }, depth + 1),
      )
    Whole(i) => {
      let m = payload.members[i]
      self.tasks.push(Assemble(BStruct(path, [(m.name, m.empty_as_absent)])))
      self.tasks.push(
        Visit(json, path, { shape: m.target, adapter: m.adapter, }, depth + 1),
      )
    }
    Positional(fixed~, rest~) =>
      match json {
        Array(items) =>
          self.positions(
            items[:],
            0,
            path,
            payload,
            fixed,
            rest,
            None,
            depth + 1,
          )
        _ =>
          self.fail(KindMismatch, path, payload.id, expected(ArrayKind, json))
      }
  }
}

///|
fn Reader::document(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  depth : Int32,
) -> Unit {
  let root = self.contract.root
  if !self.request.charge(path, root) {
    return
  }
  if depth > self.request.limits.depth {
    self.request.stop(path, root, "depth")
    return
  }
  match json {
    Null => self.results.push({ kind: LNull, source: path, })
    True => self.results.push({ kind: LBool(true), source: path, })
    False => self.results.push({ kind: LBool(false), source: path, })
    String(s) => self.results.push({ kind: LString(s), source: path, })
    Number(_, ..) =>
      match json_number(json) {
        Some(n) => self.results.push({ kind: LNumber(n), source: path, })
        None =>
          self.fail(
            Constraint(InvalidNumber),
            path,
            root,
            "I expected an exact finite numeric token.",
          )
      }
    Array(items) => {
      if !self.room(path, root, items.length()) {
        return
      }
      self.tasks.push(Assemble(BList(path, items.length())))
      for i = items.length() - 1; i >= 0; i = i - 1 {
        self.tasks.push(Document(items[i], index_path(path, i), depth + 1))
      }
    }
    Object(fields) => {
      if !self.room(path, root, fields.length()) {
        return
      }
      let names = fields.keys().to_array()
      self.tasks.push(Assemble(BObject(path, names)))
      for i = names.length() - 1; i >= 0; i = i - 1 {
        self.tasks.push(
          Document(
            fields.get(names[i]).unwrap(),
            field_path(path, names[i]),
            depth + 1,
          ),
        )
      }
    }
  }
}

///|
fn Reader::take(self : Reader, n : Int32) -> Array[LNode] {
  let items : Array[LNode] = []
  for _ in 0.. Unit {
  match build {
    BList(path, n) =>
      self.results.push({ kind: LList(self.take(n)), source: path, })
    BStruct(path, names) => {
      let items = self.take(names.length())
      let fields : Array[(String, LNode)] = []
      for i, entry in names {
        if entry.1 && items[i].kind.is_empty() {
          continue
        }
        fields.push((entry.0, items[i]))
      }
      self.results.push({ kind: LStruct(fields), source: path, })
    }
    BUnion(path, name) => {
      let payload = self.take(1)[0]
      self.results.push({ kind: LUnion(name, payload), source: path, })
    }
    BMap(path, keys) => {
      let items = self.take(keys.length())
      let fields : Array[(String, LNode)] = []
      for i, key in keys {
        fields.push((key, items[i]))
      }
      self.results.push({ kind: LMap(fields), source: path, })
    }
    BObject(path, keys) => {
      let items = self.take(keys.length())
      let fields : Array[(String, LNode)] = []
      for i, key in keys {
        fields.push((key, items[i]))
      }
      self.results.push({ kind: LObject(fields), source: path, })
    }
  }
}

///|
/// Project a parsed JSON value onto the contract. Returns no node when the request failed.
fn read_json(
  contract : @contract.WireContract,
  adapters : AdapterRegistry,
  request : Request,
  json : Json,
  path : @validation.InstancePath,
) -> LNode? {
  let reader : Reader = {
    contract,
    adapters,
    request,
    tasks: [Visit(json, path, { shape: contract.root, adapter: None, }, 0)],
    results: [],
  }
  while reader.tasks.pop() is Some(task) {
    if !request.complete {
      break
    }
    match task {
      Visit(json, path, target, depth) =>
        reader.visit(json, path, target, depth)
      Document(json, path, depth) => reader.document(json, path, depth)
      Rest(items, start, path, target, depth) => {
        if !reader.room(path, target.shape, items.length()) {
          break
        }
        reader.tasks.push(Assemble(BList(path, items.length())))
        for i = items.length() - 1; i >= 0; i = i - 1 {
          reader.tasks.push(
            Visit(items[i], index_path(path, start + i), target, depth),
          )
        }
      }
      SparseNull(path) => reader.results.push({ kind: LNull, source: path, })
      Assemble(build) => reader.assemble(build)
    }
  }
  if !request.complete || !request.findings.is_empty() {
    return None
  }
  reader.results.pop()
}

///|
/// Choose among a union's own layout and its reader-only layouts for an object input. Internal and
/// adjacent layouts apply when their tag field is present; an adjacent layout also needs its content
/// field and then wins over an internal layout with the same tag. An external layout applies to an
/// object with one field that names a member.
fn Reader::accepted_layout(
  self : Reader,
  json : Json,
  path : @validation.InstancePath,
  shape : @contract.ShapeContract,
  main : @contract.UnionLayout,
) -> @contract.UnionLayout? {
  guard json is Object(fields) else {
    if json is Array(_) && !(main is TagArray(..)) {
      for alternative in shape.accepts {
        if alternative is TagArray(..) {
          return Some(alternative)
        }
      }
    }
    return Some(main)
  }
  let names_member = fn(name : String) {
    shape.members
    .iter()
    .any(m => {
      m.tag.to_string() == name ||
      m.aliases.iter().any(a => a.to_string() == name)
    })
  }
  let applies = fn(layout : @contract.UnionLayout) {
    match layout {
      External => fields.length() == 1 && fields.keys().iter().all(names_member)
      Internal(tag~) => fields.contains(tag.to_string())
      Adjacent(tag~, content~) =>
        fields.contains(tag.to_string()) && fields.contains(content.to_string())
      _ => false
    }
  }
  let candidates = ([main] + shape.accepts).filter(applies)
  let adjacent_tags = candidates.filter_map(l => {
    match l {
      Adjacent(tag~, ..) => Some(tag)
      _ => None
    }
  })
  let chosen = candidates.filter(l => {
    !(l is Internal(tag~) && adjacent_tags.contains(tag))
  })
  match chosen {
    [] => Some(main)
    [one] => Some(one)
    _ => {
      self.fail(
        AmbiguousForm,
        path,
        shape.id,
        "I found \{chosen.length()} union layouts that read this object.",
      )
      None
    }
  }
}