///|
/// Revision equality is stricter than document meaning. It includes the source,
/// resource key, lexical metadata and every constructed value. No allocation
/// counter, clock, hash-only identity or mutable token pointer is involved.
priv struct DocumentRevision { document : Document; resource_id : String? }

///|
fn same_origin(a : SourceOrigin?, b : SourceOrigin?, cache : Array[(SourceOrigin,SourceOrigin)]) -> Bool {
  match (a,b) {
    (None,None) => true
    (Some(a),Some(b)) => {
      if physical_equal(a,b) || cache.iter().any(p => physical_equal(a,p.0) && physical_equal(b,p.1)) { return true }
      if a != b { return false }
      if cache.length() < 8 { cache.push((a,b)) }; true
    }
    _ => false
  }
}

///|
fn same_revision(a : DocumentRevision, b : DocumentRevision) -> Bool {
  if physical_equal(a,b) { return true }
  if a.resource_id != b.resource_id { return false }
  let a=a.document; let b=b.document
  let origins : Array[(SourceOrigin,SourceOrigin)] = []
  if !same_origin(a.origin,b.origin,origins) || a.prolog_spans != b.prolog_spans || a.epilog_spans != b.epilog_spans { return false }
  let stack : Array[(Content,Content)] = [(Element(a.root),Element(b.root))]
  for (ac,bc) in [(a.prolog,b.prolog),(a.epilog,b.epilog)] {
    if ac.length() != bc.length() { return false }
    for i in 0.. {
        if physical_equal(a,b) { continue }
        if !same_origin(a.origin,b.origin,origins) { return false }
        // An unmodified source element's closed scope is determined by its
        // complete source identity and span. Edited/constructed values still
        // compare their complete captured context.
        let source_context=a.captured_context && b.captured_context && a.origin != None && a.span != None && a.span == b.span
        if a.kind != b.kind || a.value_type != b.value_type || a.text_value != b.text_value || a.span != b.span || a.content_spans != b.content_spans || a.raw_name != b.raw_name || a.bindings != b.bindings || a.captured_context != b.captured_context || (!source_context && a.context != b.context) || a.values.length() != b.values.length() || a.contents.length() != b.contents.length() { return false }
        for i in 0.. v.token()) != b.text_value.map(v => v.token()) { return false }
        for i in 0.. if a != b { return false }
      (ProcessingInstruction(target=a,data=ad),ProcessingInstruction(target=b,data=bd)) => if a != b || ad != bd { return false }
      _ => return false
    }
  }
  true
}