harn-stdlib 0.10.171

Embedded Harn standard library source catalog
Documentation
fn __edit_whitespace(ch: string) -> bool {
  return ch == " " || ch == "\n" || ch == "\t" || ch == "\r"
}

fn __edit_delimiter_run(text: string, index: int) -> bool {
  if index < 0 || index >= len(text) {
    return false
  }
  const ch = text[index:index + 1]
  return (ch == "\\" || ch == "\"" || ch == "'")
    && ((index > 0 && text[index - 1:index] == ch)
      || (index + 1 < len(text) && text[index + 1:index + 2] == ch))
}

fn __edit_whitespace_key(
  text: string,
  preserve_escapes: bool,
  preserve_literals: bool = false,
) -> string {
  let out = ""
  let index = 0
  let delimiter = ""
  while index < len(text) {
    const ch = text[index:index + 1]
    if preserve_literals && delimiter != "" {
      out = "${out}${ch}"
      index = index + 1
      if ch == "\\" && index < len(text) {
        out = "${out}${text[index:index + 1]}"
        index = index + 1
      } else if ch == delimiter {
        delimiter = ""
      }
      continue
    }
    if preserve_literals && ["\"", "'", "`"].contains(ch) {
      delimiter = ch
    }
    const start = index
    if __edit_whitespace(text[index:index + 1]) {
      while index < len(text) && __edit_whitespace(text[index:index + 1]) {
        index = index + 1
      }
      if preserve_escapes
        && (__edit_delimiter_run(text, start - 1) || __edit_delimiter_run(text, index)) {
        out = "${out}${text[start:index]}"
      }
    } else {
      out = "${out}${text[index:index + 1]}"
      index = index + 1
    }
  }
  return out
}

/**
 * Compare fuzzy anchors while preserving quoted literal bytes and escapes.
 * Token spacing and indentation remain the matcher's separate responsibility.
 * @effects: []
 * @errors: []
 */
pub fn edit_anchor_literals_match(old_text: string, candidate: string) -> bool {
  return __edit_whitespace_key(old_text, true, true)
    == __edit_whitespace_key(candidate, true, true)
}

fn __edit_inside_quotes(text: string, index: int) -> bool {
  let cursor = 0
  let delimiter = ""
  while cursor < index {
    const ch = text[cursor:cursor + 1]
    if delimiter != "" && ch == "\\" {
      cursor = cursor + 2
      continue
    }
    if delimiter != "" && ch == delimiter {
      delimiter = ""
    } else if delimiter == "" && ["\"", "'", "`"].contains(ch) {
      delimiter = ch
    }
    cursor = cursor + 1
  }
  return delimiter != ""
}

/**
 * Whether only whitespace changed, retaining escape-sensitive delimiter runs.
 * @effects: []
 * @errors: []
 */
pub fn edit_is_whitespace_only_change(old_text: string, new_text: string) -> bool {
  return old_text != ""
    && (old_text == new_text
      || (regex_replace("[ \\t\\r\\n]+", "", old_text)
        == regex_replace("[ \\t\\r\\n]+", "", new_text)
        && __edit_whitespace_key(old_text, true) == __edit_whitespace_key(new_text, true)))
}

/**
 * Whether all changed whitespace belongs to quoted content.
 * @effects: []
 * @errors: []
 */
pub fn edit_whitespace_delta_inside_quotes(old_text: string, new_text: string) -> bool {
  let old_index = 0
  let new_index = 0
  let changed = false
  while old_index < len(old_text) || new_index < len(new_text) {
    if old_index < len(old_text) && new_index < len(new_text)
      && old_text[old_index:old_index + 1] == new_text[new_index:new_index + 1] {
      old_index = old_index + 1
      new_index = new_index + 1
      continue
    }
    const old_start = old_index
    const new_start = new_index
    while old_index < len(old_text) && __edit_whitespace(old_text[old_index:old_index + 1]) {
      old_index = old_index + 1
    }
    while new_index < len(new_text) && __edit_whitespace(new_text[new_index:new_index + 1]) {
      new_index = new_index + 1
    }
    if old_index == old_start && new_index == new_start {
      return false
    }
    changed = true
    const probe = min(old_start, max(len(old_text) - 1, 0))
    if !__edit_inside_quotes(old_text, probe) {
      return false
    }
  }
  return changed
}

/**
 * Reject inert formatting while allowing meaningful indentation and string repairs.
 * @effects: []
 * @errors: []
 */
pub fn edit_whitespace_change_is_rejectable(
  old_text: string,
  new_text: string,
  indentation_sensitive: bool = false,
) -> bool {
  return edit_is_whitespace_only_change(old_text, new_text)
    && (old_text == new_text
      || (!indentation_sensitive && !edit_whitespace_delta_inside_quotes(old_text, new_text)))
}

/**
 * Whether replacement retains the complete anchor at either boundary.
 * @effects: []
 * @errors: []
 */
pub fn edit_preserves_replaced_span(old_text: string, new_text: string) -> bool {
  return old_text != "" && len(new_text) > len(old_text)
    && (new_text.starts_with(old_text) || new_text.ends_with(old_text))
}

/**
 * Detect a small anchor replaced with a file-sized blob, allowing append/prepend.
 * @effects: []
 * @errors: []
 */
pub fn edit_patch_growth_risk(change: EditPatchGrowth) -> bool {
  const {before, after, old_text, new_text} = change
  if edit_preserves_replaced_span(old_text, new_text) {
    return false
  }
  const before_lines = len(split(before, "\n"))
  const after_lines = len(split(after, "\n"))
  const old_lines = len(split(old_text, "\n"))
  const new_lines = len(split(new_text, "\n"))
  return before_lines >= 20 && after_lines >= before_lines + 40
    && after_lines * 100 / max(before_lines, 1) >= 150
    && new_lines * 100 / max(old_lines, 1) >= 500
    && old_lines * 100 / max(before_lines, 1) <= 30
}

pub type EditPatchGrowth = {before: string, after: string, old_text: string, new_text: string}