///|
fn code_at(text : String, index : Int) -> Int {
text[index].to_int()
}
///|
fn slice(text : String, start : Int, end : Int) -> String {
text[start:end].to_owned()
}
///|
fn is_ascii_space(code : Int) -> Bool {
code == 32 || code == 9 || code == 10 || code == 13
}
///|
fn trim_ascii(text : String) -> String {
let mut start = 0
let mut end = text.length()
while start < end && is_ascii_space(code_at(text, start)) {
start += 1
}
while end > start && is_ascii_space(code_at(text, end - 1)) {
end -= 1
}
slice(text, start, end)
}
///|
fn split_lines(text : String) -> Array[String] {
let lines : Array[String] = []
let mut start = 0
let mut index = 0
while index < text.length() {
if code_at(text, index) == 10 {
let mut end = index
if end > start && code_at(text, end - 1) == 13 {
end -= 1
}
lines.push(slice(text, start, end))
start = index + 1
}
index += 1
}
if start <= text.length() {
let mut end = text.length()
if end > start && code_at(text, end - 1) == 13 {
end -= 1
}
lines.push(slice(text, start, end))
}
lines
}
///|
fn starts_with(text : String, prefix : String) -> Bool {
prefix.length() <= text.length() && slice(text, 0, prefix.length()) == prefix
}
///|
fn contains(text : String, needle : String) -> Bool {
if needle.length() == 0 {
return true
}
if needle.length() > text.length() {
return false
}
let limit = text.length() - needle.length()
let mut index = 0
while index <= limit {
if slice(text, index, index + needle.length()) == needle {
return true
}
index += 1
}
false
}
///|
fn ascii_lower_piece(code : Int, original : String) -> String {
match code {
65 => "a"
66 => "b"
67 => "c"
68 => "d"
69 => "e"
70 => "f"
71 => "g"
72 => "h"
73 => "i"
74 => "j"
75 => "k"
76 => "l"
77 => "m"
78 => "n"
79 => "o"
80 => "p"
81 => "q"
82 => "r"
83 => "s"
84 => "t"
85 => "u"
86 => "v"
87 => "w"
88 => "x"
89 => "y"
90 => "z"
_ => original
}
}
///|
fn ascii_lower(text : String) -> String {
let mut out = ""
let mut index = 0
while index < text.length() {
out = out +
ascii_lower_piece(code_at(text, index), slice(text, index, index + 1))
index += 1
}
out
}
///|
fn is_digit(code : Int) -> Bool {
code >= 48 && code <= 57
}
///|
fn parse_int(value : String, fallback : Int) -> Int {
let text = trim_ascii(value)
if text.length() == 0 {
return fallback
}
let mut index = 0
let mut sign = 1
if code_at(text, 0) == 45 {
sign = -1
index = 1
}
let mut result = 0
if index == text.length() {
return fallback
}
while index < text.length() {
let code = code_at(text, index)
if !is_digit(code) {
return fallback
}
result = result * 10 + code - 48
index += 1
}
result * sign
}
///|
fn parse_bool(value : String, fallback : Bool) -> Bool {
match ascii_lower(trim_ascii(value)) {
"true" => true
"yes" => true
"1" => true
"false" => false
"no" => false
"0" => false
_ => fallback
}
}
///|
fn split_csv(value : String) -> Array[String] {
let result : Array[String] = []
let mut start = 0
let mut index = 0
while index <= value.length() {
if index == value.length() || code_at(value, index) == 44 {
let item = trim_ascii(slice(value, start, index))
if item.length() > 0 {
result.push(item)
}
start = index + 1
}
index += 1
}
result
}
///|
fn split_words_quoted(text : String) -> Array[String] {
let result : Array[String] = []
let mut current = ""
let mut quoted = false
let mut escaped = false
let mut index = 0
while index < text.length() {
let code = code_at(text, index)
if escaped {
current = current + slice(text, index, index + 1)
escaped = false
} else if quoted && code == 92 {
escaped = true
} else if code == 34 {
quoted = !quoted
} else if !quoted && is_ascii_space(code) {
if current.length() > 0 {
result.push(current)
current = ""
}
} else {
current = current + slice(text, index, index + 1)
}
index += 1
}
if escaped {
current = current + "\\"
}
if current.length() > 0 {
result.push(current)
}
result
}
///|
fn token_value(tokens : Array[String], key : String) -> String? {
let prefix = key + "="
let mut index = 0
while index < tokens.length() {
if starts_with(tokens[index], prefix) {
return Some(slice(tokens[index], prefix.length(), tokens[index].length()))
}
index += 1
}
None
}
///|
fn bool_text(value : Bool) -> String {
if value {
"true"
} else {
"false"
}
}
///|
fn json_escape(value : String) -> String {
let mut out = ""
let mut index = 0
while index < value.length() {
let code = code_at(value, index)
if code == 34 {
out = out + "\\\""
} else if code == 92 {
out = out + "\\\\"
} else if code == 10 {
out = out + "\\n"
} else if code == 13 {
out = out + "\\r"
} else if code == 9 {
out = out + "\\t"
} else {
out = out + slice(value, index, index + 1)
}
index += 1
}
out
}
///|
fn is_valid_identifier(value : String) -> Bool {
if value.length() == 0 {
return false
}
let first = code_at(value, 0)
if !((first >= 65 && first <= 90) ||
(first >= 97 && first <= 122) ||
first == 95) {
return false
}
let mut index = 1
while index < value.length() {
let code = code_at(value, index)
if !((code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
(code >= 48 && code <= 57) ||
code == 95 ||
code == 45) {
return false
}
index += 1
}
true
}