///|
fn string_methods() -> Map[
String,
(String, Array[@value.Value], Map[String, @value.Value], @ast.Span) -> @value.Value raise StarlarkError,
] {
let m : Map[
String,
(String, Array[@value.Value], Map[String, @value.Value], @ast.Span) -> @value.Value raise StarlarkError,
] = {}
m["capitalize"] = string_capitalize
m["count"] = string_count
m["endswith"] = string_endswith
m["startswith"] = string_startswith
m["find"] = string_find
m["rfind"] = string_rfind
m["index"] = string_index
m["rindex"] = string_rindex
m["format"] = string_format
m["join"] = string_join
m["split"] = string_split
m["rsplit"] = string_rsplit
m["lower"] = string_lower
m["upper"] = string_upper
m["title"] = string_title
m["strip"] = string_strip
m["lstrip"] = string_lstrip
m["rstrip"] = string_rstrip
m["replace"] = string_replace
m["removeprefix"] = string_removeprefix
m["removesuffix"] = string_removesuffix
m["partition"] = string_partition
m["rpartition"] = string_rpartition
m["splitlines"] = string_splitlines
m["isalpha"] = string_isalpha
m["isdigit"] = string_isdigit
m["isalnum"] = string_isalnum
m["islower"] = string_islower
m["isupper"] = string_isupper
m["isspace"] = string_isspace
m["istitle"] = string_istitle
m["elems"] = string_elems
m
}
///|
fn string_capitalize(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="capitalize() takes no arguments (\{pos.length()} given)",
span~,
)
}
if s.length() == 0 {
return @value.Value::String("")
}
let buf = StringBuilder::new()
buf.write_char(code_to_upper(s[0].to_int()).unsafe_to_char())
for i = 1; i < s.length(); i = i + 1 {
buf.write_char(code_to_lower(s[i].to_int()).unsafe_to_char())
}
@value.Value::String(buf.to_string())
}
///|
fn string_count(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() >= 1 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="count() takes at least 1 and at most 3 arguments (\{pos.length()} given)",
span~,
)
}
let sub = expect_string(pos[0], "count", span)
let start = if pos.length() >= 2 {
clamp_index(expect_int(pos[1], "count", span), s.length())
} else {
0
}
let end = if pos.length() >= 3 {
clamp_index(expect_int(pos[2], "count", span), s.length())
} else {
s.length()
}
let count = str_count(s, sub, start, end)
@value.Value::Int(count.to_int64())
}
///|
fn string_endswith(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="endswith() takes exactly 1 argument (\{pos.length()} given)",
span~,
)
}
let suffix = expect_string(pos[0], "endswith", span)
@value.Value::Bool(str_endswith(s, suffix))
}
///|
fn string_startswith(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="startswith() takes exactly 1 argument (\{pos.length()} given)",
span~,
)
}
let prefix = expect_string(pos[0], "startswith", span)
@value.Value::Bool(str_startswith(s, prefix))
}
///|
fn string_find(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() >= 1 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="find() takes at least 1 and at most 3 arguments (\{pos.length()} given)",
span~,
)
}
let sub = expect_string(pos[0], "find", span)
let start = if pos.length() >= 2 {
clamp_index(expect_int(pos[1], "find", span), s.length())
} else {
0
}
let end = if pos.length() >= 3 {
clamp_index(expect_int(pos[2], "find", span), s.length())
} else {
s.length()
}
let idx = str_find(s, sub, start, end)
@value.Value::Int(idx.to_int64())
}
///|
fn string_rfind(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() >= 1 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="rfind() takes at least 1 and at most 3 arguments (\{pos.length()} given)",
span~,
)
}
let sub = expect_string(pos[0], "rfind", span)
let start = if pos.length() >= 2 {
clamp_index(expect_int(pos[1], "rfind", span), s.length())
} else {
0
}
let end = if pos.length() >= 3 {
clamp_index(expect_int(pos[2], "rfind", span), s.length())
} else {
s.length()
}
let idx = str_rfind(s, sub, start, end)
@value.Value::Int(idx.to_int64())
}
///|
fn string_index(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() >= 1 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="index() takes at least 1 and at most 3 arguments (\{pos.length()} given)",
span~,
)
}
let sub = expect_string(pos[0], "index", span)
let start = if pos.length() >= 2 {
clamp_index(expect_int(pos[1], "index", span), s.length())
} else {
0
}
let end = if pos.length() >= 3 {
clamp_index(expect_int(pos[2], "index", span), s.length())
} else {
s.length()
}
let idx = str_find(s, sub, start, end)
if idx < 0 {
raise StarlarkError::ValueError(message="substring not found", span~)
}
@value.Value::Int(idx.to_int64())
}
///|
fn string_rindex(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() >= 1 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="rindex() takes at least 1 and at most 3 arguments (\{pos.length()} given)",
span~,
)
}
let sub = expect_string(pos[0], "rindex", span)
let start = if pos.length() >= 2 {
clamp_index(expect_int(pos[1], "rindex", span), s.length())
} else {
0
}
let end = if pos.length() >= 3 {
clamp_index(expect_int(pos[2], "rindex", span), s.length())
} else {
s.length()
}
let idx = str_rfind(s, sub, start, end)
if idx < 0 {
raise StarlarkError::ValueError(message="substring not found", span~)
}
@value.Value::Int(idx.to_int64())
}
///|
fn string_format(
s : String,
pos : Array[@value.Value],
kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
// format supports {}, {0}, {1}, and {name} placeholders
let buf = StringBuilder::new()
let mut auto_idx = 0
let mut i = 0
while i < s.length() {
let code = s[i].to_int()
if code == '{'.to_int() {
if i + 1 < s.length() && s[i + 1].to_int() == '{'.to_int() {
buf.write_char('{')
i = i + 2
continue
}
// Find matching }
let start_idx = i + 1
let mut end_idx = start_idx
while end_idx < s.length() && s[end_idx].to_int() != '}'.to_int() {
end_idx = end_idx + 1
}
if end_idx >= s.length() {
raise StarlarkError::ValueError(
message="Single '{' encountered in format string",
span~,
)
}
let field = substr(s, start_idx, end_idx)
if field == "" {
// Auto-numbering
if auto_idx >= pos.length() {
raise StarlarkError::IndexError(
message="Replacement index \{auto_idx} out of range for positional args tuple",
span~,
)
}
buf.write_string(value_to_str(pos[auto_idx]))
auto_idx = auto_idx + 1
} else if is_all_digits(field) {
let idx = parse_simple_int(field)
if idx >= pos.length() {
raise StarlarkError::IndexError(
message="Replacement index \{idx} out of range for positional args tuple",
span~,
)
}
buf.write_string(value_to_str(pos[idx]))
} else {
// Named field
match kw.get(field) {
Some(v) => buf.write_string(value_to_str(v))
None => raise StarlarkError::KeyError(key=field, span~)
}
}
i = end_idx + 1
} else if code == '}'.to_int() {
if i + 1 < s.length() && s[i + 1].to_int() == '}'.to_int() {
buf.write_char('}')
i = i + 2
continue
}
raise StarlarkError::ValueError(
message="Single '}' encountered in format string",
span~,
)
} else {
buf.write_char(code.unsafe_to_char())
i = i + 1
}
}
@value.Value::String(buf.to_string())
}
///|
fn string_join(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="join() takes exactly 1 argument (\{pos.length()} given)",
span~,
)
}
let items = iterable_to_array(pos[0], span)
let parts : Array[String] = []
for item in items {
match item {
String(str) => parts.push(str)
_ =>
raise StarlarkError::TypeError(
message="sequence item: expected str instance, got '\{item.type_name()}'",
span~,
)
}
}
@value.Value::String(parts.join(s))
}
///|
fn string_split(
s : String,
pos : Array[@value.Value],
kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() <= 2 else {
raise StarlarkError::TypeError(
message="split() takes at most 2 arguments (\{pos.length()} given)",
span~,
)
}
let sep : String? = if pos.length() >= 1 {
match pos[0] {
None => None
String(str) => Some(str)
_ =>
raise StarlarkError::TypeError(
message="split() argument must be str or None, not '\{pos[0].type_name()}'",
span~,
)
}
} else {
match kw.get("sep") {
Some(String(str)) => Some(str)
Some(None) => None
Some(other) =>
raise StarlarkError::TypeError(
message="split() argument must be str or None, not '\{other.type_name()}'",
span~,
)
None => None
}
}
let maxsplit : Int = if pos.length() >= 2 {
expect_int(pos[1], "split", span)
} else {
match kw.get("maxsplit") {
Some(Int(n)) => n.to_int()
Some(_) => -1
None => -1
}
}
let parts = str_split(s, sep, maxsplit)
let result : Array[@value.Value] = []
for part in parts {
result.push(@value.Value::String(part))
}
@value.Value::List(Ref::new(result))
}
///|
fn string_rsplit(
s : String,
pos : Array[@value.Value],
kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() <= 2 else {
raise StarlarkError::TypeError(
message="rsplit() takes at most 2 arguments (\{pos.length()} given)",
span~,
)
}
let sep : String? = if pos.length() >= 1 {
match pos[0] {
None => None
String(str) => Some(str)
_ =>
raise StarlarkError::TypeError(
message="rsplit() argument must be str or None, not '\{pos[0].type_name()}'",
span~,
)
}
} else {
None
}
let maxsplit : Int = if pos.length() >= 2 {
expect_int(pos[1], "rsplit", span)
} else {
match kw.get("maxsplit") {
Some(Int(n)) => n.to_int()
Some(_) => -1
None => -1
}
}
let parts = str_rsplit(s, sep, maxsplit)
let result : Array[@value.Value] = []
for part in parts {
result.push(@value.Value::String(part))
}
@value.Value::List(Ref::new(result))
}
///|
fn string_lower(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="lower() takes no arguments (\{pos.length()} given)",
span~,
)
}
let buf = StringBuilder::new()
for i = 0; i < s.length(); i = i + 1 {
buf.write_char(code_to_lower(s[i].to_int()).unsafe_to_char())
}
@value.Value::String(buf.to_string())
}
///|
fn string_upper(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="upper() takes no arguments (\{pos.length()} given)",
span~,
)
}
let buf = StringBuilder::new()
for i = 0; i < s.length(); i = i + 1 {
buf.write_char(code_to_upper(s[i].to_int()).unsafe_to_char())
}
@value.Value::String(buf.to_string())
}
///|
fn string_title(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="title() takes no arguments (\{pos.length()} given)",
span~,
)
}
let buf = StringBuilder::new()
let mut prev_is_letter = false
for i = 0; i < s.length(); i = i + 1 {
let code = s[i].to_int()
if is_alpha_code(code) {
if not(prev_is_letter) {
buf.write_char(code_to_upper(code).unsafe_to_char())
} else {
buf.write_char(code_to_lower(code).unsafe_to_char())
}
prev_is_letter = true
} else {
buf.write_char(code.unsafe_to_char())
prev_is_letter = false
}
}
@value.Value::String(buf.to_string())
}
///|
fn string_strip(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() <= 1 else {
raise StarlarkError::TypeError(
message="strip() takes at most 1 argument (\{pos.length()} given)",
span~,
)
}
let chars : String? = if pos.length() == 1 {
match pos[0] {
None => None
String(str) => Some(str)
_ =>
raise StarlarkError::TypeError(
message="strip() argument must be str or None, not '\{pos[0].type_name()}'",
span~,
)
}
} else {
None
}
@value.Value::String(str_strip(s, chars))
}
///|
fn string_lstrip(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() <= 1 else {
raise StarlarkError::TypeError(
message="lstrip() takes at most 1 argument (\{pos.length()} given)",
span~,
)
}
let chars : String? = if pos.length() == 1 {
match pos[0] {
None => None
String(str) => Some(str)
_ =>
raise StarlarkError::TypeError(
message="lstrip() argument must be str or None, not '\{pos[0].type_name()}'",
span~,
)
}
} else {
None
}
@value.Value::String(str_lstrip(s, chars))
}
///|
fn string_rstrip(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() <= 1 else {
raise StarlarkError::TypeError(
message="rstrip() takes at most 1 argument (\{pos.length()} given)",
span~,
)
}
let chars : String? = if pos.length() == 1 {
match pos[0] {
None => None
String(str) => Some(str)
_ =>
raise StarlarkError::TypeError(
message="rstrip() argument must be str or None, not '\{pos[0].type_name()}'",
span~,
)
}
} else {
None
}
@value.Value::String(str_rstrip(s, chars))
}
///|
fn string_replace(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() >= 2 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="replace() takes at least 2 and at most 3 arguments (\{pos.length()} given)",
span~,
)
}
let old = expect_string(pos[0], "replace", span)
let new_str = expect_string(pos[1], "replace", span)
let count : Int = if pos.length() >= 3 {
expect_int(pos[2], "replace", span)
} else {
-1
}
@value.Value::String(str_replace(s, old, new_str, count))
}
///|
fn string_removeprefix(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="removeprefix() takes exactly 1 argument (\{pos.length()} given)",
span~,
)
}
let prefix = expect_string(pos[0], "removeprefix", span)
if str_startswith(s, prefix) {
@value.Value::String(substr(s, prefix.length(), s.length()))
} else {
@value.Value::String(s)
}
}
///|
fn string_removesuffix(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="removesuffix() takes exactly 1 argument (\{pos.length()} given)",
span~,
)
}
let suffix = expect_string(pos[0], "removesuffix", span)
if suffix.length() > 0 && str_endswith(s, suffix) {
@value.Value::String(substr(s, 0, s.length() - suffix.length()))
} else {
@value.Value::String(s)
}
}
///|
fn string_partition(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="partition() takes exactly 1 argument (\{pos.length()} given)",
span~,
)
}
let sep = expect_string(pos[0], "partition", span)
if sep == "" {
raise StarlarkError::ValueError(message="empty separator", span~)
}
let idx = str_find(s, sep, 0, s.length())
if idx >= 0 {
@value.Value::Tuple([
@value.Value::String(substr(s, 0, idx)),
@value.Value::String(sep),
@value.Value::String(substr(s, idx + sep.length(), s.length())),
])
} else {
@value.Value::Tuple([
@value.Value::String(s),
@value.Value::String(""),
@value.Value::String(""),
])
}
}
///|
fn string_rpartition(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="rpartition() takes exactly 1 argument (\{pos.length()} given)",
span~,
)
}
let sep = expect_string(pos[0], "rpartition", span)
if sep == "" {
raise StarlarkError::ValueError(message="empty separator", span~)
}
let idx = str_rfind(s, sep, 0, s.length())
if idx >= 0 {
@value.Value::Tuple([
@value.Value::String(substr(s, 0, idx)),
@value.Value::String(sep),
@value.Value::String(substr(s, idx + sep.length(), s.length())),
])
} else {
@value.Value::Tuple([
@value.Value::String(""),
@value.Value::String(""),
@value.Value::String(s),
])
}
}
///|
fn string_splitlines(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() <= 1 else {
raise StarlarkError::TypeError(
message="splitlines() takes at most 1 argument (\{pos.length()} given)",
span~,
)
}
let keepends = if pos.length() == 1 { pos[0].is_truthy() } else { false }
let lines = str_splitlines(s, keepends)
let result : Array[@value.Value] = []
for line in lines {
result.push(@value.Value::String(line))
}
@value.Value::List(Ref::new(result))
}
///|
fn string_isalpha(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="isalpha() takes no arguments (\{pos.length()} given)",
span~,
)
}
@value.Value::Bool(s.length() > 0 && str_all_codes(s, is_alpha_code))
}
///|
fn string_isdigit(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="isdigit() takes no arguments (\{pos.length()} given)",
span~,
)
}
@value.Value::Bool(s.length() > 0 && str_all_codes(s, is_digit_code))
}
///|
fn string_isalnum(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="isalnum() takes no arguments (\{pos.length()} given)",
span~,
)
}
@value.Value::Bool(s.length() > 0 && str_all_codes(s, is_alnum_code))
}
///|
fn string_islower(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="islower() takes no arguments (\{pos.length()} given)",
span~,
)
}
let mut has_cased = false
let mut all_lower = true
for i = 0; i < s.length(); i = i + 1 {
let code = s[i].to_int()
if is_upper_code(code) {
all_lower = false
break
}
if is_lower_code(code) {
has_cased = true
}
}
@value.Value::Bool(has_cased && all_lower)
}
///|
fn string_isupper(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="isupper() takes no arguments (\{pos.length()} given)",
span~,
)
}
let mut has_cased = false
let mut all_upper = true
for i = 0; i < s.length(); i = i + 1 {
let code = s[i].to_int()
if is_lower_code(code) {
all_upper = false
break
}
if is_upper_code(code) {
has_cased = true
}
}
@value.Value::Bool(has_cased && all_upper)
}
///|
fn string_isspace(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="isspace() takes no arguments (\{pos.length()} given)",
span~,
)
}
@value.Value::Bool(s.length() > 0 && str_all_codes(s, is_space_code))
}
///|
fn string_istitle(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="istitle() takes no arguments (\{pos.length()} given)",
span~,
)
}
@value.Value::Bool(str_is_title(s))
}
///|
fn string_elems(
s : String,
pos : Array[@value.Value],
_kw : Map[String, @value.Value],
span : @ast.Span,
) -> @value.Value raise StarlarkError {
guard pos.length() == 0 else {
raise StarlarkError::TypeError(
message="elems() takes no arguments (\{pos.length()} given)",
span~,
)
}
let result : Array[@value.Value] = []
for i = 0; i < s.length(); i = i + 1 {
result.push(@value.Value::String(string_char_at(s, i)))
}
@value.Value::List(Ref::new(result))
}
// ===== Char code point helper functions =====
// All work with Int (code point) to avoid UInt16/Char type issues
///|
fn code_to_upper(code : Int) -> Int {
if code >= 'a'.to_int() && code <= 'z'.to_int() {
code - 32
} else {
code
}
}
///|
fn code_to_lower(code : Int) -> Int {
if code >= 'A'.to_int() && code <= 'Z'.to_int() {
code + 32
} else {
code
}
}
///|
fn is_alpha_code(code : Int) -> Bool {
(code >= 'a'.to_int() && code <= 'z'.to_int()) ||
(code >= 'A'.to_int() && code <= 'Z'.to_int())
}
///|
fn is_digit_code(code : Int) -> Bool {
code >= '0'.to_int() && code <= '9'.to_int()
}
///|
fn is_alnum_code(code : Int) -> Bool {
is_alpha_code(code) || is_digit_code(code)
}
///|
fn is_upper_code(code : Int) -> Bool {
code >= 'A'.to_int() && code <= 'Z'.to_int()
}
///|
fn is_lower_code(code : Int) -> Bool {
code >= 'a'.to_int() && code <= 'z'.to_int()
}
///|
fn is_space_code(code : Int) -> Bool {
code == ' '.to_int() ||
code == '\t'.to_int() ||
code == '\n'.to_int() ||
code == '\r'.to_int() ||
code == 0x0b || // vertical tab
code == 0x0c // form feed
}
///|
fn str_all_codes(s : String, pred : (Int) -> Bool) -> Bool {
for i = 0; i < s.length(); i = i + 1 {
if not(pred(s[i].to_int())) {
return false
}
}
true
}
///|
fn str_is_title(s : String) -> Bool {
let mut has_cased = false
let mut prev_is_letter = false
for i = 0; i < s.length(); i = i + 1 {
let code = s[i].to_int()
if is_upper_code(code) {
if prev_is_letter {
return false
}
has_cased = true
prev_is_letter = true
} else if is_lower_code(code) {
if not(prev_is_letter) {
return false
}
has_cased = true
prev_is_letter = true
} else {
prev_is_letter = false
}
}
has_cased
}
///|
fn substr(s : String, start : Int, end : Int) -> String {
let buf = StringBuilder::new()
for i = start; i < end; i = i + 1 {
buf.write_char(s[i].to_int().unsafe_to_char())
}
buf.to_string()
}
///|
fn str_find(s : String, sub : String, start : Int, end : Int) -> Int {
if sub.length() == 0 {
return start
}
if sub.length() > end - start {
return -1
}
for i = start; i <= end - sub.length(); i = i + 1 {
let mut found = true
for j = 0; j < sub.length(); j = j + 1 {
if s[i + j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
-1
}
///|
fn str_rfind(s : String, sub : String, start : Int, end : Int) -> Int {
if sub.length() == 0 {
return end
}
if sub.length() > end - start {
return -1
}
for i = end - sub.length(); i >= start; i = i - 1 {
let mut found = true
for j = 0; j < sub.length(); j = j + 1 {
if s[i + j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
-1
}
///|
fn str_count(s : String, sub : String, start : Int, end : Int) -> Int {
if sub.length() == 0 {
return end - start + 1
}
let mut count = 0
let mut i = start
while i <= end - sub.length() {
let idx = str_find(s, sub, i, end)
if idx < 0 {
break
}
count = count + 1
i = idx + sub.length()
}
count
}
///|
fn str_startswith(s : String, prefix : String) -> Bool {
if prefix.length() > s.length() {
return false
}
for i = 0; i < prefix.length(); i = i + 1 {
if s[i] != prefix[i] {
return false
}
}
true
}
///|
fn str_endswith(s : String, suffix : String) -> Bool {
if suffix.length() > s.length() {
return false
}
let offset = s.length() - suffix.length()
for i = 0; i < suffix.length(); i = i + 1 {
if s[offset + i] != suffix[i] {
return false
}
}
true
}
///|
fn str_replace(
s : String,
old : String,
new_str : String,
count : Int,
) -> String {
if old.length() == 0 {
let buf = StringBuilder::new()
let mut replacements = 0
let max = if count < 0 { s.length() + 1 } else { count }
for i = 0; i < s.length(); i = i + 1 {
if replacements < max {
buf.write_string(new_str)
replacements = replacements + 1
}
buf.write_char(s[i].to_int().unsafe_to_char())
}
if replacements < max {
buf.write_string(new_str)
}
return buf.to_string()
}
let buf = StringBuilder::new()
let mut i = 0
let mut replacements = 0
while i < s.length() {
if (count < 0 || replacements < count) && i + old.length() <= s.length() {
let idx = str_find(s, old, i, s.length())
if idx == i {
buf.write_string(new_str)
i = i + old.length()
replacements = replacements + 1
continue
}
}
buf.write_char(s[i].to_int().unsafe_to_char())
i = i + 1
}
buf.to_string()
}
///|
fn str_split(s : String, sep : String?, maxsplit : Int) -> Array[String] {
match sep {
None => {
let result : Array[String] = []
let mut i = 0
let mut splits = 0
while i < s.length() {
while i < s.length() && is_space_code(s[i].to_int()) {
i = i + 1
}
if i >= s.length() {
break
}
if maxsplit >= 0 && splits >= maxsplit {
result.push(substr(s, i, s.length()))
break
} else {
let start = i
while i < s.length() && not(is_space_code(s[i].to_int())) {
i = i + 1
}
result.push(substr(s, start, i))
splits = splits + 1
}
}
result
}
Some(sep_str) => {
if sep_str.length() == 0 {
let result : Array[String] = []
result.push(s)
return result
}
let result : Array[String] = []
let mut i = 0
let mut splits = 0
while i <= s.length() {
if maxsplit >= 0 && splits >= maxsplit {
result.push(substr(s, i, s.length()))
break
} else {
let idx = str_find(s, sep_str, i, s.length())
if idx < 0 {
result.push(substr(s, i, s.length()))
break
} else {
result.push(substr(s, i, idx))
i = idx + sep_str.length()
splits = splits + 1
}
}
}
result
}
}
}
///|
fn str_rsplit(s : String, sep : String?, maxsplit : Int) -> Array[String] {
match sep {
None => {
let parts : Array[String] = []
let mut i = s.length() - 1
let mut splits = 0
while i >= 0 {
while i >= 0 && is_space_code(s[i].to_int()) {
i = i - 1
}
if i < 0 {
break
}
if maxsplit >= 0 && splits >= maxsplit {
parts.push(substr(s, 0, i + 1))
break
} else {
let end = i + 1
while i >= 0 && not(is_space_code(s[i].to_int())) {
i = i - 1
}
parts.push(substr(s, i + 1, end))
splits = splits + 1
}
}
let result : Array[String] = []
for j = parts.length() - 1; j >= 0; j = j - 1 {
result.push(parts[j])
}
result
}
Some(sep_str) => {
if sep_str.length() == 0 {
let result : Array[String] = []
result.push(s)
return result
}
let parts : Array[String] = []
let mut i = s.length()
let mut splits = 0
while i >= 0 {
if maxsplit >= 0 && splits >= maxsplit {
parts.push(substr(s, 0, i))
break
} else {
let idx = str_rfind(s, sep_str, 0, i)
if idx < 0 {
parts.push(substr(s, 0, i))
break
} else {
parts.push(substr(s, idx + sep_str.length(), i))
i = idx
splits = splits + 1
}
}
}
let result : Array[String] = []
for j = parts.length() - 1; j >= 0; j = j - 1 {
result.push(parts[j])
}
result
}
}
}
///|
fn str_strip(s : String, chars : String?) -> String {
str_rstrip(str_lstrip(s, chars), chars)
}
///|
fn str_lstrip(s : String, chars : String?) -> String {
let mut start = 0
match chars {
None =>
while start < s.length() && is_space_code(s[start].to_int()) {
start = start + 1
}
Some(ch) =>
while start < s.length() && str_contains_code(ch, s[start].to_int()) {
start = start + 1
}
}
substr(s, start, s.length())
}
///|
fn str_rstrip(s : String, chars : String?) -> String {
let mut end = s.length()
match chars {
None =>
while end > 0 && is_space_code(s[end - 1].to_int()) {
end = end - 1
}
Some(ch) =>
while end > 0 && str_contains_code(ch, s[end - 1].to_int()) {
end = end - 1
}
}
substr(s, 0, end)
}
///|
fn str_contains_code(s : String, code : Int) -> Bool {
for i = 0; i < s.length(); i = i + 1 {
if s[i].to_int() == code {
return true
}
}
false
}
///|
fn str_splitlines(s : String, keepends : Bool) -> Array[String] {
let result : Array[String] = []
let mut i = 0
while i < s.length() {
let start = i
while i < s.length() &&
s[i].to_int() != '\n'.to_int() &&
s[i].to_int() != '\r'.to_int() {
i = i + 1
}
if i < s.length() {
if s[i].to_int() == '\r'.to_int() &&
i + 1 < s.length() &&
s[i + 1].to_int() == '\n'.to_int() {
if keepends {
result.push(substr(s, start, i + 2))
} else {
result.push(substr(s, start, i))
}
i = i + 2
} else {
if keepends {
result.push(substr(s, start, i + 1))
} else {
result.push(substr(s, start, i))
}
i = i + 1
}
} else if start < s.length() {
result.push(substr(s, start, i))
}
}
result
}
///|
fn expect_string(
v : @value.Value,
method_name : String,
span : @ast.Span,
) -> String raise StarlarkError {
match v {
String(s) => s
_ =>
raise StarlarkError::TypeError(
message="\{method_name}() argument must be str, not '\{v.type_name()}'",
span~,
)
}
}
///|
fn expect_int(
v : @value.Value,
method_name : String,
span : @ast.Span,
) -> Int raise StarlarkError {
match v {
Int(n) => n.to_int()
_ =>
raise StarlarkError::TypeError(
message="\{method_name}() argument must be int, not '\{v.type_name()}'",
span~,
)
}
}
///|
fn clamp_index(idx : Int, len : Int) -> Int {
if idx < 0 {
let result = idx + len
if result < 0 {
0
} else {
result
}
} else if idx > len {
len
} else {
idx
}
}
///|
fn is_all_digits(s : String) -> Bool {
if s.length() == 0 {
return false
}
for i = 0; i < s.length(); i = i + 1 {
if not(is_digit_code(s[i].to_int())) {
return false
}
}
true
}
///|
fn parse_simple_int(s : String) -> Int {
let mut result = 0
for i = 0; i < s.length(); i = i + 1 {
result = result * 10 + (s[i].to_int() - '0'.to_int())
}
result
}