///|
fn words(line : String) -> Array[String] {
  let out : Array[String] = []
  for part in line.split(" ") {
    for word in part.split("\t") {
      let text = word.trim().to_owned()
      if text != "" {
        out.push(text)
      }
    }
  }
  out
}

///|
/// Bounded line DSL: interval NAME; rel LEFT RIGHT SET [ASCII-LABEL].
/// Only whole-line # comments are recognized. Declarations may follow relations.
pub fn parse_dsl(text : String) -> Network raise AllenError {
  if text.length() > 1048576 {
    raise TooLarge(text.length())
  }
  let names : Array[String] = []
  let constraints : Array[Constraint] = []
  let mut line_number = 0
  for raw in text.split("\n") {
    line_number = line_number + 1
    if line_number > 10000 {
      raise TooLarge(line_number)
    }
    let line = raw.trim().to_owned()
    if line == "" || line.has_prefix("#") {
      continue
    }
    let fields = words(line)
    if fields.length() == 2 && fields[0] == "interval" {
      if names.length() >= 64 {
        raise TooLarge(names.length() + 1)
      }
      names.push(fields[1])
    } else if (fields.length() == 4 || fields.length() == 5) &&
      fields[0] == "rel" {
      if constraints.length() >= 4096 {
        raise TooLarge(constraints.length() + 1)
      }
      let label = if fields.length() == 5 {
        fields[4]
      } else {
        "line-\{line_number}"
      }
      if !valid_name(label) {
        raise InvalidFormat("DSL label must be an ASCII name")
      }
      constraints.push({
        left: fields[1],
        right: fields[2],
        allowed: parse_relset(fields[3]),
        label,
      })
    } else {
      raise InvalidFormat("invalid DSL statement on line \{line_number}")
    }
  }
  Network::from_constraints(names, constraints)
}

///|
/// Export the current matrix, not original provenance. Inferred domains get
/// explicit snapshot labels. The result parses to exactly the same domains.
pub fn Network::to_dsl(self : Network) -> String {
  let lines : Array[String] = [
    "# MoonAllen matrix snapshot; not original evidence",
  ]
  for name in self.names {
    lines.push("interval \{name}")
  }
  for c in self.domain_constraints(label="snapshot") {
    lines.push("rel \{c.left} \{c.right} \{c.allowed.render()} snapshot")
  }
  lines.join("\n") + "\n"
}