///|
/// A predefined character class. The spelling is kept for display.
pub(all) enum ClassKind {
  /// `.`
  Any
  /// A letter escape such as `\d`, `\W` or `\s`.
  Shorthand(Char)
  /// A Unicode property such as `\p{Lu}`.
  Property(String)
  /// A code point escape such as `\x{41}`, `A` or `\0101`.
  CodePoint(String)
  /// A POSIX bracket class such as `[[:alpha:]]`, stored as `alpha`.
  Posix(String)
} derive(Eq, Debug)

///|
pub extend ClassKind with Eq::{equal, not_equal}

///|
pub extend ClassKind with @debug.Debug::{to_repr}

///|
/// One item of a bracketed character set.
pub(all) enum SetItem {
  /// One literal character, or one escaped character.
  Char(String)
  /// An inclusive range such as `a-z`.
  Range(String, String)
  /// A letter escape inside the set, such as `\d`.
  Escape(String)
} derive(Eq, Debug)

///|
pub extend SetItem with Eq::{equal, not_equal}

///|
pub extend SetItem with @debug.Debug::{to_repr}

///|
/// A zero-width position test that consumes no input.
pub(all) enum AnchorKind {
  /// `^`
  LineStart
  /// `$`
  LineEnd
  /// `\A`
  InputStart
  /// `\Z`
  InputEndBeforeNewline
  /// `\z`
  InputEnd
  /// `\G`
  PreviousMatchEnd
  /// `\b`
  WordBoundary
  /// `\B`
  NonWordBoundary
} derive(Eq, Debug)

///|
pub extend AnchorKind with Eq::{equal, not_equal}

///|
pub extend AnchorKind with @debug.Debug::{to_repr}

///|
pub(all) enum GroupKind {
  Capturing
  NonCapturing
  Named(String)
} derive(Eq, Debug)

///|
pub extend GroupKind with Eq::{equal, not_equal}

///|
pub extend GroupKind with @debug.Debug::{to_repr}

///|
/// The direction of a lookaround assertion.
pub(all) enum Look {
  Ahead
  Behind
} derive(Eq, Debug)

///|
pub extend Look with Eq::{equal, not_equal}

///|
pub extend Look with @debug.Debug::{to_repr}

///|
/// Symbolic repetition bounds. A repeat is never expanded into copies.
pub struct Bounds {
  min : Int
  max : Int?
  reluctant : Bool
  spelling : String
} derive(Eq, Debug)

///|
pub extend Bounds with Eq::{equal, not_equal}

///|
pub extend Bounds with @debug.Debug::{to_repr}

///|
/// Make bounds. The spelling defaults to the canonical regex quantifier.
pub fn Bounds::new(
  min : Int,
  max? : Int,
  reluctant? : Bool = false,
  spelling? : String,
) -> Bounds {
  let spelling = match spelling {
    Some(text) => text
    None => {
      let base = match (min, max) {
        (0, None) => "*"
        (1, None) => "+"
        (0, Some(1)) => "?"
        (n, None) => "{\{n},}"
        (n, Some(m)) => if n == m { "{\{n}}" } else { "{\{n},\{m}}" }
      }
      if reluctant {
        base + "?"
      } else {
        base
      }
    }
  }
  { min, max, reluctant, spelling }
}

///|
/// The meaning of one expression node. Composite kinds name their children by
/// node identity, so a model is a flat, bounded arena.
pub(all) enum Kind {
  /// Consumed literal text of one or more code points.
  Literal(String)
  Class(ClassKind)
  /// A bracketed set. `true` marks a negated set such as `[^abc]`.
  Set(Bool, ReadOnlyArray[SetItem])
  Anchor(AnchorKind)
  /// A numbered backreference such as `\1`. It consumes the text of a group.
  Backreference(String)
  Sequence(ReadOnlyArray[String])
  Choice(ReadOnlyArray[String])
  Group(GroupKind, String)
  Repeat(Bounds, String)
  /// A lookaround constraint. `true` marks a positive assertion.
  Assertion(Look, Bool, String)
} derive(Eq, Debug)

///|
pub extend Kind with Eq::{equal, not_equal}

///|
pub extend Kind with @debug.Debug::{to_repr}

///|
pub struct Node {
  id : String
  kind : Kind
  source : @diagrams.Span?
} derive(Eq, Debug)

///|
pub extend Node with Eq::{equal, not_equal}

///|
pub extend Node with @debug.Debug::{to_repr}

///|
pub fn Node::new(id : String, kind : Kind, source? : @diagrams.Span) -> Node {
  { id, kind, source }
}

///|
/// The children of `node` in drawing order.
pub fn Node::children(self : Node) -> ReadOnlyArray[String] {
  match self.kind {
    Sequence(items) | Choice(items) => items
    Group(_, child) | Repeat(_, child) | Assertion(_, _, child) => [child]
    _ => []
  }
}

///|
/// Literal text, classes, sets and anchors are leaves of the railroad.
pub fn Node::is_leaf(self : Node) -> Bool {
  match self.kind {
    Literal(_) | Class(_) | Set(_, _) | Anchor(_) | Backreference(_) => true
    _ => false
  }
}

///|
/// The visible label of a leaf, or the name of a composite construct.
pub fn Node::label(self : Node) -> String {
  match self.kind {
    Literal(text) => text
    Class(Any) => "."
    Class(Shorthand(c)) => "\\" + c.to_string()
    Class(Property(name)) => "\\p{" + name + "}"
    Class(CodePoint(spelling)) => spelling
    Class(Posix(name)) => ":" + name + ":"
    Set(negated, items) => {
      let out = StringBuilder()
      if negated {
        out.write_string("not ")
      }
      for i, item in items {
        if i > 0 {
          out.write_string(" ")
        }
        match item {
          Char(c) => out.write_string(c)
          Range(a, b) => out.write_string(a + "-" + b)
          Escape(e) => out.write_string(e)
        }
      }
      out.to_string()
    }
    Anchor(kind) =>
      match kind {
        LineStart => "^"
        LineEnd => "$"
        InputStart => "\\A"
        InputEndBeforeNewline => "\\Z"
        InputEnd => "\\z"
        PreviousMatchEnd => "\\G"
        WordBoundary => "\\b"
        NonWordBoundary => "\\B"
      }
    Backreference(number) => "\\" + number
    Sequence(_) => "sequence"
    Choice(_) => "choice"
    Group(Capturing, _) => "group"
    Group(NonCapturing, _) => "?:"
    Group(Named(name), _) => name
    Repeat(bounds, _) => bounds.spelling
    Assertion(look, positive, _) =>
      match (look, positive) {
        (Ahead, true) => "?="
        (Ahead, false) => "?!"
        (Behind, true) => "?<="
        (Behind, false) => "? Regex {
  {
    id,
    root,
    nodes: ReadOnlyArray::from_array(nodes),
    title,
    accessibility_title,
    accessibility_description,
    pattern,
  }
}

///|
/// Find a node by identity. The search is linear; use `Index` for repeated lookups.
pub fn Regex::node(self : Regex, id : String) -> Node? {
  for node in self.nodes {
    if node.id == id {
      return Some(node)
    }
  }
  None
}