// Small internal helpers shared by the identifier validators.
///|
/// ASCII-only uppercase. Non-letters (and non-ASCII) are returned unchanged.
fn upper_ascii(c : Char) -> Char {
match c {
'a' => 'A'
'b' => 'B'
'c' => 'C'
'd' => 'D'
'e' => 'E'
'f' => 'F'
'g' => 'G'
'h' => 'H'
'i' => 'I'
'j' => 'J'
'k' => 'K'
'l' => 'L'
'm' => 'M'
'n' => 'N'
'o' => 'O'
'p' => 'P'
'q' => 'Q'
'r' => 'R'
's' => 'S'
't' => 'T'
'u' => 'U'
'v' => 'V'
'w' => 'W'
'x' => 'X'
'y' => 'Y'
'z' => 'Z'
_ => c
}
}
///|
/// Extract `len` characters starting at `start`. Returns None when the range
/// falls outside the string.
fn substring(s : String, start : Int, len : Int) -> String? {
let chars : Array[Char] = []
for ch in s {
chars.push(ch)
}
if start < 0 || len < 0 || start + len > chars.length() {
return None
}
let buf = StringBuilder()
for i = start; i < start + len; i = i + 1 {
buf.write_char(chars[i])
}
Some(buf.to_string())
}
///|
/// Split a string into its characters.
fn chars_of(s : String) -> Array[Char] {
let chars : Array[Char] = []
for ch in s {
chars.push(ch)
}
chars
}
///|
/// True when `c` is an ASCII digit.
fn is_digit(c : Char) -> Bool {
c >= '0' && c <= '9'
}
///|
/// True when `c` is an ASCII uppercase letter.
fn is_alpha(c : Char) -> Bool {
c >= 'A' && c <= 'Z'
}
///|
/// Numeric value of a digit or letter, expanding letters to 10..35 as the ISO
/// identifier schemes require. Returns None for anything else.
fn alnum_value(c : Char) -> Int? {
let u = upper_ascii(c)
if is_digit(u) {
Some(u.to_int() - '0'.to_int())
} else if is_alpha(u) {
Some(10 + (u.to_int() - 'A'.to_int()))
} else {
None
}
}
///|
/// A one-character string.
fn char_str(c : Char) -> String {
let buf = StringBuilder()
buf.write_char(c)
buf.to_string()
}
///|
/// The digits in order, so that a value can be turned back into a character
/// without doing arithmetic on `Char`.
let digit_chars : Array[Char] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
]
///|
/// The character for a value in 0..9. Values outside that range are clamped, so
/// callers that already range-check their input are unaffected.
fn digit_char(v : Int) -> Char {
if v < 0 {
'0'
} else if v > 9 {
'9'
} else {
digit_chars[v]
}
}
///|
/// Remainder modulo 97 after expanding every character to its numeric value:
/// digits keep their value, letters become 10..35. Returns None when a
/// character is neither.
fn alnum_mod97(cs : Array[Char]) -> Int? {
let mut rem = 0
for c in cs {
let v = match alnum_value(c) {
None => return None
Some(v) => v
}
if v >= 10 {
rem = rem * 10 + v / 10
rem = rem % 97
}
rem = rem * 10 + v % 10
rem = rem % 97
}
Some(rem)
}
///|
/// True when every character is an ASCII letter or digit.
fn alnum_only(s : String) -> Bool {
let cs = chars_of(s)
if cs.length() == 0 {
return false
}
for c in cs {
if !is_digit(c) && !is_alpha(upper_ascii(c)) {
return false
}
}
true
}
///|
/// Read the first `n` characters as an integer. None if there are too few
/// characters or any of them is not a digit.
fn leading_int(s : String, n : Int) -> Int? {
let chars = chars_of(s)
if chars.length() < n {
return None
}
let mut v = 0
for i = 0; i < n; i = i + 1 {
let c = chars[i]
if !is_digit(c) {
return None
}
v = v * 10 + (c.to_int() - '0'.to_int())
}
Some(v)
}
///|
/// True when the first `n` digits fall in `[lo, hi]`.
fn prefix_in(s : String, n : Int, lo : Int, hi : Int) -> Bool {
match leading_int(s, n) {
Some(v) => v >= lo && v <= hi
None => false
}
}
///|
/// Number of digits, or None if the string holds anything else.
fn digit_count(s : String) -> Int? {
match digits_of(s) {
Some(d) => Some(d.length())
None => None
}
}