///|
/// Bounded prescan before the core recursive parser. Duplicate object keys
/// are compared after JSON escape decoding, including escaped spellings.
fn guarded_json(text : String) -> Json raise KineticsError {
  if text.length() > 1000000 {
    raise Budget("model JSON exceeds one million UTF-16 units")
  }
  let stack : Array[Map[String, Bool]?] = []
  let mut quoted = false
  let mut escaped = false
  let mut token = StringBuilder()
  let mut pending : String? = None
  for ch in text.iter() {
    if quoted {
      token.write_char(ch)
      if escaped {
        escaped = false
      } else if ch == '\\' {
        escaped = true
      } else if ch == '"' {
        quoted = false
        let decoded = @json.parse(token.to_string()) catch {
          _ => raise Invalid("invalid JSON string")
        }
        pending = match decoded {
          String(s) => Some(s)
          _ => None
        }
      }
      continue
    }
    if ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' {
      continue
    }
    if ch == ':' {
      if stack.is_empty() {
        raise Invalid("JSON colon without object")
      }
      match (stack[stack.length() - 1], pending) {
        (Some(keys), Some(key)) => {
          if keys.contains(key) {
            raise Invalid("duplicate JSON object key")
          }
          keys[key] = true
        }
        _ => raise Invalid("JSON object key expected")
      }
      pending = None
    } else if ch == '"' {
      token = StringBuilder()
      token.write_char(ch)
      quoted = true
      pending = None
    } else {
      pending = None
      if ch == '{' {
        stack.push(Some(Map([])))
      } else if ch == '[' {
        stack.push(None)
      } else if ch == '}' || ch == ']' {
        if stack.is_empty() {
          raise Invalid("unbalanced JSON")
        }
        let _ = stack.pop()
      }
      if stack.length() > 6 {
        raise Budget("model JSON nesting exceeds six")
      }
    }
  }
  @json.parse(text) catch {
    _ => raise Invalid("invalid model JSON")
  }
}