///|
fn parse_action(s : String) -> Action raise PolicyError {
  match s {
    "allow" => Allow
    "deny" => Deny
    _ => raise Invalid("expected allow or deny")
  }
}

///|
fn parse_protocol(s : String) -> Protocol? raise PolicyError {
  match s {
    "tcp" => Some(Tcp)
    "udp" => Some(Udp)
    "any" => None
    _ => raise Invalid("expected tcp, udp or any")
  }
}

///|
/// Strict ASCII, versioned, bounded DSL. Comments and blank lines are allowed.
pub fn Policy::parse(text : String) -> Policy raise PolicyError {
  if text.length() > 131072 {
    raise Invalid("policy exceeds 128 KiB ASCII limit")
  }
  for c in text.iter() {
    if c.to_int() > 127 ||
      (c.to_int() < 32 && c != '\n' && c != '\r' && c != '\t') {
      raise Invalid("policy must be ASCII text")
    }
  }
  let mut header = false
  let mut default_action : Action? = None
  let rules : Array[Rule] = []
  for i, line in text.split("\n").to_array() {
    if i >= 1024 {
      raise Invalid("policy exceeds 1024 lines")
    }
    if line.length() > 512 {
      raise Invalid("line \{i+1}: exceeds 512 characters")
    }
    let line = line.trim().to_owned()
    if line == "" || line.has_prefix("#") {
      continue
    }
    let words = line
      .replace_all(old="\t", new=" ")
      .split(" ")
      .filter(s => s.length() > 0)
      .map(s => s.to_owned())
      .to_array()
    try {
      if !header {
        if words != ["moonpolicyproof", "1"] {
          raise Invalid("expected moonpolicyproof 1 header")
        }
        header = true
        continue
      }
      match words {
        ["default", action] => {
          if default_action is Some(_) || rules.length() > 0 {
            raise Invalid("default must occur once before rules")
          }
          default_action = Some(parse_action(action))
        }
        ["rule", id, action, proto, src, dst, sp, dp] => {
          if default_action is None {
            raise Invalid("default required before rules")
          }
          if rules.length() >= 256 {
            raise Invalid("at most 256 rules")
          }
          rules.push(
            Rule::new(
              id,
              parse_action(action),
              parse_protocol(proto),
              src,
              dst,
              Ports::parse(sp),
              Ports::parse(dp),
            ),
          )
        }
        _ => raise Invalid("unknown directive or wrong field count")
      }
    } catch {
      Invalid(e) => raise Invalid("line \{i+1}: \{e}")
      e => raise e
    }
  }
  if !header {
    raise Invalid("missing version header")
  }
  match default_action {
    Some(a) => Policy::new(rules, a)
    None => raise Invalid("missing default action")
  }
}