///|
fn is_ident_start(ch : Char) -> Bool {
  match ch {
    'A'..='Z' | 'a'..='z' | '_' => true
    _ => false
  }
}

///|
fn is_dollar_name(ch : Char) -> Bool {
  if is_ident_start(ch) {
    true
  } else {
    match ch {
      '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => true
      _ => false
    }
  }
}

///|
fn check_shell(
  inst : Instruction,
  script : String,
  diagnostics : Array[Diagnostic],
) -> Unit {
  let mut quote = ' '
  let mut i = 0
  let mut unquoted_dollar = false
  let mut unquoted_subst = false
  let mut unquoted_at = false
  while i < script.length() {
    match script.get_char(i) {
      None => break
      Some('\\') => i += 2
      Some('\'') => {
        if quote != '"' {
          quote = if quote == '\'' { ' ' } else { '\'' }
        }
        i += 1
      }
      Some('"') => {
        if quote != '\'' {
          quote = if quote == '"' { ' ' } else { '"' }
        }
        i += 1
      }
      Some('`') => {
        if quote == ' ' {
          unquoted_subst = true
        }
        i += 1
      }
      Some('$') =>
        if quote == '\'' {
          i += 1
        } else {
          match script.get_char(i + 1) {
            Some('@' | '*') => {
              if quote == ' ' {
                unquoted_at = true
              }
              i += 2
            }
            Some('(') => {
              if quote == ' ' {
                unquoted_subst = true
              }
              i += 2
            }
            Some('{') => {
              if quote == ' ' {
                unquoted_dollar = true
              }
              i += 2
            }
            Some(ch) =>
              if is_dollar_name(ch) {
                if quote == ' ' {
                  unquoted_dollar = true
                }
                i += 2
              } else {
                i += 1
              }
            None => i += 1
          }
        }
      Some(_) => i += 1
    }
  }
  if unquoted_dollar {
    diagnostics.push(
      diagnostic(
        "SC2086",
        "warning",
        inst.line,
        "Double quote to prevent globbing and word splitting.",
      ),
    )
  }
  if unquoted_subst {
    diagnostics.push(
      diagnostic(
        "SC2046",
        "warning",
        inst.line,
        "Quote this to prevent word splitting.",
      ),
    )
  }
  if unquoted_at {
    diagnostics.push(
      diagnostic(
        "SC2068",
        "warning",
        inst.line,
        "Double quote array expansions to avoid re-splitting.",
      ),
    )
  }
  if contains_token(script, "cd") && !script.contains("||") {
    diagnostics.push(
      diagnostic(
        "SC2164",
        "warning",
        inst.line,
        "Use `cd ... || exit` in case cd fails.",
      ),
    )
  }
}