///|
const ASCII_PROPERTY_NAME : Regex = re"^[A-Za-z_$][A-Za-z0-9_$]*$"

///|
const PROPERTY_QUOTE_START : Regex = re"^['\"`]"

///|
/// Decode static ASCII identifier keys, including JS escapes. Other values
/// cannot collide with the ASCII property names generated by this pass.
fn quoted_property_name(source : String, start : Int, end : Int) -> String? {
  let output = StringBuilder()
  for remaining = source.view(start_offset=start + 1, end_offset=end - 1) {
    let (digits, radix, rest) = lexmatch remaining with longest {
      (re"^[A-Za-z0-9_$]+" as literal, after=rest) => {
        output.write_view(literal)
        continue rest
      }
      (re"^\\(?:\r\n|[\n\r\u2028\u2029])", after=rest) => continue rest
      (re"^\\x" + (re"[0-9A-Fa-f]{2}" as digits), after=rest) =>
        (digits, 16, rest)
      (re"^\\u" + (re"[0-9A-Fa-f]{4}" as digits), after=rest) =>
        (digits, 16, rest)
      (re"^\\u[{]" + (re"[0-9A-Fa-f]+" as digits) + re"[}]", after=rest) =>
        (digits, 16, rest)
      (
        re"^\\" +
        ((re"[0-3][0-7]{0,2}" | re"[4-7][0-7]?") as digits),
        after=rest,
      ) => (digits, 8, rest)
      // Incomplete numeric escapes and control escapes cannot name a property.
      (re"^\\[bfnrtvux]", after=_) => return None
      (re"^\\" + (re"[A-Za-z0-9_$]" as character), after=rest) => {
        output.write_char(character)
        continue rest
      }
      re"^$" => break
      _ => return None
    }
    guard decode_escape_digits(digits, radix) is Some(code_point) else {
      return None
    }
    guard code_point < 0x80 && code_point.to_char() is Some(character) else {
      return None
    }
    output.write_char(character)
    continue rest
  }
  let name = output.to_string()
  guard name =~ ASCII_PROPERTY_NAME else { return None }
  Some(name)
}

///|
fn Program::property_leaf(program : Program, id : Int) -> Int {
  for current = id {
    let node = program.nodes[current]
    guard node.kind == Parenthesized else { break current }
    continue node.a
  }
}