// Reading a shrubbery tree: spans, group shapes, and term predicates.

///|
/// A source span, as offsets into the file.
pub using @er {type Span}

///|
/// The span of something that was never written down.
pub let nowhere : Span = { start: -1, len: 0, }

///|
/// True when a span points at real source text.
pub fn has_source(s : Span) -> Bool {
  s.start >= 0
}

///|
/// A shrubbery span as an error-report span.
///
/// `idx` is the UTF-16 index shrubbery keeps for exactly this purpose; `line`
/// and `pos` are code-point positions for humans and are not used here.
pub fn to_span(s : @shbasic.Span) -> Span {
  { start: s.start.idx, len: s.end.idx - s.start.idx, }
}

///|
/// The span of a shrubbery node.
pub fn node_span(n : @sh.Node) -> Span {
  to_span(n.span)
}

///|
/// The span covering a run of terms, or `nowhere` if there are none.
pub fn terms_span(ts : ArrayView[@sh.Node]) -> Span {
  if ts.length() == 0 {
    nowhere
  } else {
    let a = node_span(ts[0])
    let b = node_span(ts[ts.length() - 1])
    { start: a.start, len: b.start + b.len - a.start, }
  }
}

///|
/// A group split into the three parts shrubbery guarantees it can have: the
/// terms, then at most one block, then at most one run of alternatives.
///
/// This shape is not a convention of wap's, it is the shrubbery grammar --
/// "a block list appears only at the end of a group list or just before an
/// alts list that is at the end of the group list".
pub(all) struct Parts {
  head : Array[@sh.Node]
  block : Array[@sh.Node]?
  alts : Array[Array[@sh.Node]]?
  span : Span
}

///|
/// The children of a node that holds a sequence, or a single-element array.
fn children(n : @sh.Node) -> Array[@sh.Node] {
  match n.it {
    Group(xs)
    | Block(xs)
    | Alts(xs)
    | Parens(xs)
    | Brackets(xs)
    | Braces(xs)
    | Quotes(xs)
    | Multi(xs) => xs
    _ => [n]
  }
}

///|
/// Split a group into head terms, block and alternatives.
pub fn split(n : @sh.Node) -> Parts {
  let all = children(n)
  let head = []
  let mut block = None
  let mut alts = None
  for t in all {
    match t.it {
      Block(gs) => block = Some(gs)
      Alts(bs) => {
        let arms = []
        for b in bs {
          arms.push(children(b))
        }
        alts = Some(arms)
      }
      _ => head.push(t)
    }
  }
  { head, block, alts, span: node_span(n), }
}

///|
/// The identifier a term is, if it is one.
pub fn as_id(n : @sh.Node) -> String? {
  match n.it {
    Id(s) => Some(s)
    _ => None
  }
}

///|
/// The operator a term is, if it is one.
pub fn as_op(n : @sh.Node) -> String? {
  match n.it {
    Op(s) => Some(s)
    _ => None
  }
}

///|
/// The keyword a term is, if it is one. Keywords are wap's labels: `~outer`.
pub fn as_kw(n : @sh.Node) -> String? {
  match n.it {
    Kw(s) => Some(s)
    _ => None
  }
}

///|
/// True when the term is exactly this identifier.
pub fn is_id(n : @sh.Node, name : String) -> Bool {
  as_id(n) is Some(s) && s == name
}

///|
/// True when the term is exactly this operator.
pub fn is_op(n : @sh.Node, name : String) -> Bool {
  as_op(n) is Some(s) && s == name
}

///|
/// The string a literal term is, if it is a string.
pub fn as_str(n : @sh.Node) -> String? {
  match n.it {
    Lit(Str(s)) => Some(s)
    _ => None
  }
}

///|
/// The source text of a term, which is how numeric literals keep the spelling
/// they were written with. Shrubbery parses `0xFF` to 255; Wax needs the digits
/// back, because the checker's flexible-literal typing and the printer both
/// depend on them.
pub fn raw_text(n : @sh.Node) -> String {
  // `to_source` replays the node's metadata, which includes the whitespace it
  // was written after. A literal's spelling is what is wanted here, not its
  // layout.
  n.to_source().trim().to_owned()
}