2011-12-13 18:25:51 -06:00
|
|
|
/*
|
|
|
|
Module: str
|
|
|
|
|
|
|
|
String manipulation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
export eq, lteq, hash, is_empty, is_not_empty, is_whitespace, byte_len,
|
|
|
|
byte_len_range, index,
|
2011-12-19 12:08:36 -06:00
|
|
|
rindex, find, starts_with, ends_with, substr, slice, split, splitn,
|
2012-01-15 22:20:06 -06:00
|
|
|
split_str, split_func, split_char, lines, lines_any, words,
|
|
|
|
concat, connect, to_lower, to_upper, replace, char_slice,
|
2012-01-01 10:30:12 -06:00
|
|
|
trim_left, trim_right, trim, unshift_char, shift_char, pop_char,
|
|
|
|
push_char, is_utf8, from_chars, to_chars, char_len, char_len_range,
|
|
|
|
char_at, bytes, is_ascii, shift_byte, pop_byte,
|
2011-12-13 18:25:51 -06:00
|
|
|
unsafe_from_byte, unsafe_from_bytes, from_char, char_range_at,
|
2012-01-03 12:08:13 -06:00
|
|
|
from_cstr, sbuf, as_buf, push_byte, utf8_char_width, safe_slice,
|
2011-12-13 18:25:51 -06:00
|
|
|
contains, iter_chars, loop_chars, loop_chars_sub,
|
|
|
|
escape;
|
|
|
|
|
|
|
|
#[abi = "cdecl"]
|
|
|
|
native mod rustrt {
|
|
|
|
fn rust_str_push(&s: str, ch: u8);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: eq
|
|
|
|
|
|
|
|
Bytewise string equality
|
|
|
|
*/
|
2011-12-16 10:38:49 -06:00
|
|
|
pure fn eq(&&a: str, &&b: str) -> bool { a == b }
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
Function: lteq
|
|
|
|
|
|
|
|
Bytewise less than or equal
|
|
|
|
*/
|
2011-12-16 10:38:49 -06:00
|
|
|
pure fn lteq(&&a: str, &&b: str) -> bool { a <= b }
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
Function: hash
|
|
|
|
|
|
|
|
String hash function
|
|
|
|
*/
|
|
|
|
fn hash(&&s: str) -> uint {
|
|
|
|
// djb hash.
|
|
|
|
// FIXME: replace with murmur.
|
|
|
|
|
|
|
|
let u: uint = 5381u;
|
|
|
|
for c: u8 in s { u *= 33u; u += c as uint; }
|
|
|
|
ret u;
|
|
|
|
}
|
|
|
|
|
|
|
|
// UTF-8 tags and ranges
|
|
|
|
const tag_cont_u8: u8 = 128u8;
|
|
|
|
const tag_cont: uint = 128u;
|
|
|
|
const max_one_b: uint = 128u;
|
|
|
|
const tag_two_b: uint = 192u;
|
|
|
|
const max_two_b: uint = 2048u;
|
|
|
|
const tag_three_b: uint = 224u;
|
|
|
|
const max_three_b: uint = 65536u;
|
|
|
|
const tag_four_b: uint = 240u;
|
|
|
|
const max_four_b: uint = 2097152u;
|
|
|
|
const tag_five_b: uint = 248u;
|
|
|
|
const max_five_b: uint = 67108864u;
|
|
|
|
const tag_six_b: uint = 252u;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: is_utf8
|
|
|
|
|
|
|
|
Determines if a vector uf bytes contains valid UTF-8
|
|
|
|
*/
|
|
|
|
fn is_utf8(v: [u8]) -> bool {
|
|
|
|
let i = 0u;
|
|
|
|
let total = vec::len::<u8>(v);
|
|
|
|
while i < total {
|
|
|
|
let chsize = utf8_char_width(v[i]);
|
|
|
|
if chsize == 0u { ret false; }
|
|
|
|
if i + chsize > total { ret false; }
|
|
|
|
i += 1u;
|
|
|
|
while chsize > 1u {
|
|
|
|
if v[i] & 192u8 != tag_cont_u8 { ret false; }
|
|
|
|
i += 1u;
|
|
|
|
chsize -= 1u;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: is_ascii
|
|
|
|
|
|
|
|
Determines if a string contains only ASCII characters
|
|
|
|
*/
|
|
|
|
fn is_ascii(s: str) -> bool {
|
|
|
|
let i: uint = byte_len(s);
|
|
|
|
while i > 0u { i -= 1u; if s[i] & 128u8 != 0u8 { ret false; } }
|
|
|
|
ret true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Predicate: is_empty
|
|
|
|
|
|
|
|
Returns true if the string has length 0
|
|
|
|
*/
|
|
|
|
pure fn is_empty(s: str) -> bool { for c: u8 in s { ret false; } ret true; }
|
|
|
|
|
|
|
|
/*
|
|
|
|
Predicate: is_not_empty
|
|
|
|
|
|
|
|
Returns true if the string has length greater than 0
|
|
|
|
*/
|
|
|
|
pure fn is_not_empty(s: str) -> bool { !is_empty(s) }
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: is_whitespace
|
|
|
|
|
|
|
|
Returns true if the string contains only whitespace
|
|
|
|
*/
|
|
|
|
fn is_whitespace(s: str) -> bool {
|
2012-01-02 14:14:20 -06:00
|
|
|
ret loop_chars(s, char::is_whitespace);
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: byte_len
|
|
|
|
|
|
|
|
Returns the length in bytes of a string
|
|
|
|
*/
|
2011-12-16 10:38:49 -06:00
|
|
|
pure fn byte_len(s: str) -> uint unsafe {
|
2011-12-13 18:25:51 -06:00
|
|
|
let v: [u8] = unsafe::reinterpret_cast(s);
|
|
|
|
let vlen = vec::len(v);
|
|
|
|
unsafe::leak(v);
|
|
|
|
// There should always be a null terminator
|
|
|
|
assert (vlen > 0u);
|
|
|
|
ret vlen - 1u;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: byte_len_range
|
|
|
|
|
|
|
|
As byte_len but for a substring
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
s - A string
|
|
|
|
byte_offset - The byte offset at which to start in the string
|
|
|
|
char_len - The number of chars (not bytes!) in the range
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The number of bytes in the substring starting at `byte_offset` and
|
|
|
|
containing `char_len` chars.
|
|
|
|
|
|
|
|
Safety note:
|
|
|
|
|
|
|
|
This function fails if `byte_offset` or `char_len` do not represent
|
|
|
|
valid positions in `s`
|
|
|
|
*/
|
|
|
|
fn byte_len_range(s: str, byte_offset: uint, char_len: uint) -> uint {
|
|
|
|
let i = byte_offset;
|
|
|
|
let chars = 0u;
|
|
|
|
while chars < char_len {
|
|
|
|
let chsize = utf8_char_width(s[i]);
|
|
|
|
assert (chsize > 0u);
|
|
|
|
i += chsize;
|
|
|
|
chars += 1u;
|
|
|
|
}
|
|
|
|
ret i - byte_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: bytes
|
|
|
|
|
|
|
|
Converts a string to a vector of bytes. The result vector is not
|
|
|
|
null-terminated.
|
|
|
|
*/
|
|
|
|
fn bytes(s: str) -> [u8] unsafe {
|
|
|
|
let v = unsafe::reinterpret_cast(s);
|
|
|
|
let vcopy = vec::slice(v, 0u, vec::len(v) - 1u);
|
|
|
|
unsafe::leak(v);
|
|
|
|
ret vcopy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: unsafe_from_bytes
|
|
|
|
|
|
|
|
Converts a vector of bytes to a string. Does not verify that the
|
|
|
|
vector contains valid UTF-8.
|
|
|
|
*/
|
|
|
|
fn unsafe_from_bytes(v: [const u8]) -> str unsafe {
|
|
|
|
let vcopy: [u8] = v + [0u8];
|
|
|
|
let scopy: str = unsafe::reinterpret_cast(vcopy);
|
|
|
|
unsafe::leak(vcopy);
|
|
|
|
ret scopy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: unsafe_from_byte
|
|
|
|
|
|
|
|
Converts a byte to a string. Does not verify that the byte is
|
|
|
|
valid UTF-8.
|
|
|
|
*/
|
|
|
|
fn unsafe_from_byte(u: u8) -> str { unsafe_from_bytes([u]) }
|
|
|
|
|
|
|
|
fn push_utf8_bytes(&s: str, ch: char) {
|
|
|
|
let code = ch as uint;
|
|
|
|
let bytes =
|
|
|
|
if code < max_one_b {
|
|
|
|
[code as u8]
|
|
|
|
} else if code < max_two_b {
|
|
|
|
[code >> 6u & 31u | tag_two_b as u8, code & 63u | tag_cont as u8]
|
|
|
|
} else if code < max_three_b {
|
|
|
|
[code >> 12u & 15u | tag_three_b as u8,
|
|
|
|
code >> 6u & 63u | tag_cont as u8, code & 63u | tag_cont as u8]
|
|
|
|
} else if code < max_four_b {
|
|
|
|
[code >> 18u & 7u | tag_four_b as u8,
|
|
|
|
code >> 12u & 63u | tag_cont as u8,
|
|
|
|
code >> 6u & 63u | tag_cont as u8, code & 63u | tag_cont as u8]
|
|
|
|
} else if code < max_five_b {
|
|
|
|
[code >> 24u & 3u | tag_five_b as u8,
|
|
|
|
code >> 18u & 63u | tag_cont as u8,
|
|
|
|
code >> 12u & 63u | tag_cont as u8,
|
|
|
|
code >> 6u & 63u | tag_cont as u8, code & 63u | tag_cont as u8]
|
|
|
|
} else {
|
|
|
|
[code >> 30u & 1u | tag_six_b as u8,
|
|
|
|
code >> 24u & 63u | tag_cont as u8,
|
|
|
|
code >> 18u & 63u | tag_cont as u8,
|
|
|
|
code >> 12u & 63u | tag_cont as u8,
|
|
|
|
code >> 6u & 63u | tag_cont as u8, code & 63u | tag_cont as u8]
|
|
|
|
};
|
|
|
|
push_bytes(s, bytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: from_char
|
|
|
|
|
|
|
|
Convert a char to a string
|
|
|
|
*/
|
|
|
|
fn from_char(ch: char) -> str {
|
|
|
|
let buf = "";
|
|
|
|
push_utf8_bytes(buf, ch);
|
|
|
|
ret buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: from_chars
|
|
|
|
|
|
|
|
Convert a vector of chars to a string
|
|
|
|
*/
|
|
|
|
fn from_chars(chs: [char]) -> str {
|
|
|
|
let buf = "";
|
|
|
|
for ch: char in chs { push_utf8_bytes(buf, ch); }
|
|
|
|
ret buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: utf8_char_width
|
|
|
|
|
2012-01-15 22:20:06 -06:00
|
|
|
Given a first byte, determine how many bytes are in this UTF-8 character
|
2011-12-13 18:25:51 -06:00
|
|
|
*/
|
2011-12-16 10:38:49 -06:00
|
|
|
pure fn utf8_char_width(b: u8) -> uint {
|
2011-12-13 18:25:51 -06:00
|
|
|
let byte: uint = b as uint;
|
|
|
|
if byte < 128u { ret 1u; }
|
|
|
|
if byte < 192u {
|
|
|
|
ret 0u; // Not a valid start byte
|
|
|
|
|
|
|
|
}
|
|
|
|
if byte < 224u { ret 2u; }
|
|
|
|
if byte < 240u { ret 3u; }
|
|
|
|
if byte < 248u { ret 4u; }
|
|
|
|
if byte < 252u { ret 5u; }
|
|
|
|
ret 6u;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: char_range_at
|
|
|
|
|
|
|
|
Pluck a character out of a string and return the index of the next character.
|
|
|
|
This function can be used to iterate over the unicode characters of a string.
|
|
|
|
|
|
|
|
Example:
|
2012-01-15 22:20:06 -06:00
|
|
|
> let s = "中华Việt Nam";
|
|
|
|
> let i = 0u;
|
|
|
|
> while i < str::byte_len(s) {
|
|
|
|
> let {ch, next} = str::char_range_at(s, i);
|
|
|
|
> std::io::println(#fmt("%u: %c",i,ch));
|
|
|
|
> i = next;
|
2011-12-13 18:25:51 -06:00
|
|
|
> }
|
|
|
|
|
2012-01-15 22:20:06 -06:00
|
|
|
Example output:
|
|
|
|
|
|
|
|
0: 中
|
|
|
|
3: 华
|
|
|
|
6: V
|
|
|
|
7: i
|
|
|
|
8: ệ
|
|
|
|
11: t
|
|
|
|
12:
|
|
|
|
13: N
|
|
|
|
14: a
|
|
|
|
15: m
|
|
|
|
|
2011-12-13 18:25:51 -06:00
|
|
|
Parameters:
|
|
|
|
|
|
|
|
s - The string
|
|
|
|
i - The byte offset of the char to extract
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
A record {ch: char, next: uint} containing the char value and the byte
|
|
|
|
index of the next unicode character.
|
|
|
|
|
|
|
|
Failure:
|
|
|
|
|
|
|
|
If `i` is greater than or equal to the length of the string.
|
|
|
|
If `i` is not the index of the beginning of a valid UTF-8 character.
|
|
|
|
*/
|
|
|
|
fn char_range_at(s: str, i: uint) -> {ch: char, next: uint} {
|
|
|
|
let b0 = s[i];
|
|
|
|
let w = utf8_char_width(b0);
|
|
|
|
assert (w != 0u);
|
|
|
|
if w == 1u { ret {ch: b0 as char, next: i + 1u}; }
|
|
|
|
let val = 0u;
|
|
|
|
let end = i + w;
|
|
|
|
let i = i + 1u;
|
|
|
|
while i < end {
|
|
|
|
let byte = s[i];
|
|
|
|
assert (byte & 192u8 == tag_cont_u8);
|
|
|
|
val <<= 6u;
|
|
|
|
val += byte & 63u8 as uint;
|
|
|
|
i += 1u;
|
|
|
|
}
|
|
|
|
// Clunky way to get the right bits from the first byte. Uses two shifts,
|
|
|
|
// the first to clip off the marker bits at the left of the byte, and then
|
|
|
|
// a second (as uint) to get it to the right position.
|
|
|
|
val += (b0 << (w + 1u as u8) as uint) << ((w - 1u) * 6u - w - 1u);
|
|
|
|
ret {ch: val as char, next: i};
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: char_at
|
|
|
|
|
|
|
|
Pluck a character out of a string
|
|
|
|
*/
|
|
|
|
fn char_at(s: str, i: uint) -> char { ret char_range_at(s, i).ch; }
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: iter_chars
|
|
|
|
|
|
|
|
Iterate over the characters in a string
|
|
|
|
*/
|
|
|
|
|
|
|
|
fn iter_chars(s: str, it: block(char)) {
|
|
|
|
let pos = 0u, len = byte_len(s);
|
|
|
|
while (pos < len) {
|
|
|
|
let {ch, next} = char_range_at(s, pos);
|
|
|
|
pos = next;
|
|
|
|
it(ch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: loop_chars
|
|
|
|
|
|
|
|
Loop through a string, char by char
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
s - A string to traverse. It may be empty.
|
|
|
|
it - A block to execute with each consecutive character of `s`.
|
|
|
|
Return `true` to continue, `false` to stop.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
`true` If execution proceeded correctly, `false` if it was interrupted,
|
|
|
|
that is if `it` returned `false` at any point.
|
|
|
|
*/
|
|
|
|
fn loop_chars(s: str, it: block(char) -> bool) -> bool{
|
|
|
|
ret loop_chars_sub(s, 0u, byte_len(s), it);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: loop_chars_sub
|
|
|
|
|
|
|
|
Loop through a substring, char by char
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
s - A string to traverse. It may be empty.
|
|
|
|
byte_offset - The byte offset at which to start in the string.
|
|
|
|
byte_len - The number of bytes to traverse in the string
|
|
|
|
it - A block to execute with each consecutive character of `s`.
|
|
|
|
Return `true` to continue, `false` to stop.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
`true` If execution proceeded correctly, `false` if it was interrupted,
|
|
|
|
that is if `it` returned `false` at any point.
|
|
|
|
|
|
|
|
Safety note:
|
|
|
|
- This function does not check whether the substring is valid.
|
|
|
|
- This function fails if `byte_offset` or `byte_len` do not
|
|
|
|
represent valid positions inside `s`
|
|
|
|
*/
|
|
|
|
fn loop_chars_sub(s: str, byte_offset: uint, byte_len: uint,
|
|
|
|
it: block(char) -> bool) -> bool {
|
|
|
|
let i = byte_offset;
|
|
|
|
let result = true;
|
|
|
|
while i < byte_len {
|
|
|
|
let {ch, next} = char_range_at(s, i);
|
|
|
|
if !it(ch) {result = false; break;}
|
|
|
|
i = next;
|
|
|
|
}
|
|
|
|
ret result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: char_len
|
|
|
|
|
|
|
|
Count the number of unicode characters in a string
|
|
|
|
*/
|
|
|
|
fn char_len(s: str) -> uint {
|
|
|
|
ret char_len_range(s, 0u, byte_len(s));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: char_len_range
|
|
|
|
|
|
|
|
As char_len but for a slice of a string
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
s - A valid string
|
|
|
|
byte_start - The position inside `s` where to start counting in bytes.
|
|
|
|
byte_len - The number of bytes of `s` to take into account.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The number of Unicode characters in `s` in
|
|
|
|
segment [byte_start, byte_start+len( .
|
|
|
|
|
|
|
|
Safety note:
|
|
|
|
- This function does not check whether the substring is valid.
|
|
|
|
- This function fails if `byte_offset` or `byte_len` do not
|
|
|
|
represent valid positions inside `s`
|
|
|
|
*/
|
|
|
|
fn char_len_range(s: str, byte_start: uint, byte_len: uint) -> uint {
|
|
|
|
let i = byte_start;
|
|
|
|
let len = 0u;
|
|
|
|
while i < byte_len {
|
|
|
|
let chsize = utf8_char_width(s[i]);
|
|
|
|
assert (chsize > 0u);
|
|
|
|
len += 1u;
|
|
|
|
i += chsize;
|
|
|
|
}
|
|
|
|
assert (i == byte_len);
|
|
|
|
ret len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: to_chars
|
|
|
|
|
|
|
|
Convert a string to a vector of characters
|
|
|
|
*/
|
|
|
|
fn to_chars(s: str) -> [char] {
|
|
|
|
let buf: [char] = [];
|
|
|
|
let i = 0u;
|
|
|
|
let len = byte_len(s);
|
|
|
|
while i < len {
|
|
|
|
let cur = char_range_at(s, i);
|
|
|
|
buf += [cur.ch];
|
|
|
|
i = cur.next;
|
|
|
|
}
|
|
|
|
ret buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: push_char
|
|
|
|
|
|
|
|
Append a character to a string
|
|
|
|
*/
|
|
|
|
fn push_char(&s: str, ch: char) { s += from_char(ch); }
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: pop_char
|
|
|
|
|
|
|
|
Remove the final character from a string and return it.
|
|
|
|
|
|
|
|
Failure:
|
|
|
|
|
|
|
|
If the string does not contain any characters.
|
|
|
|
*/
|
|
|
|
fn pop_char(&s: str) -> char {
|
|
|
|
let end = byte_len(s);
|
|
|
|
while end > 0u && s[end - 1u] & 192u8 == tag_cont_u8 { end -= 1u; }
|
|
|
|
assert (end > 0u);
|
|
|
|
let ch = char_at(s, end - 1u);
|
|
|
|
s = substr(s, 0u, end - 1u);
|
|
|
|
ret ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: shift_char
|
|
|
|
|
|
|
|
Remove the first character from a string and return it.
|
|
|
|
|
|
|
|
Failure:
|
|
|
|
|
|
|
|
If the string does not contain any characters.
|
|
|
|
*/
|
|
|
|
fn shift_char(&s: str) -> char {
|
|
|
|
let r = char_range_at(s, 0u);
|
|
|
|
s = substr(s, r.next, byte_len(s) - r.next);
|
|
|
|
ret r.ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: unshift_char
|
|
|
|
|
|
|
|
Prepend a char to a string
|
|
|
|
*/
|
|
|
|
fn unshift_char(&s: str, ch: char) { s = from_char(ch) + s; }
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: index
|
|
|
|
|
|
|
|
Returns the index of the first matching byte. Returns -1 if
|
|
|
|
no match is found.
|
|
|
|
*/
|
|
|
|
fn index(s: str, c: u8) -> int {
|
|
|
|
let i: int = 0;
|
|
|
|
for k: u8 in s { if k == c { ret i; } i += 1; }
|
|
|
|
ret -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: rindex
|
|
|
|
|
|
|
|
Returns the index of the last matching byte. Returns -1
|
|
|
|
if no match is found.
|
|
|
|
*/
|
|
|
|
fn rindex(s: str, c: u8) -> int {
|
|
|
|
let n: int = byte_len(s) as int;
|
|
|
|
while n >= 0 { if s[n] == c { ret n; } n -= 1; }
|
|
|
|
ret n;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: find
|
|
|
|
|
|
|
|
Finds the index of the first matching substring.
|
|
|
|
Returns -1 if `haystack` does not contain `needle`.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
haystack - The string to look in
|
|
|
|
needle - The string to look for
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
The index of the first occurance of `needle`, or -1 if not found.
|
|
|
|
*/
|
|
|
|
fn find(haystack: str, needle: str) -> int {
|
|
|
|
let haystack_len: int = byte_len(haystack) as int;
|
|
|
|
let needle_len: int = byte_len(needle) as int;
|
|
|
|
if needle_len == 0 { ret 0; }
|
|
|
|
fn match_at(haystack: str, needle: str, i: int) -> bool {
|
|
|
|
let j: int = i;
|
|
|
|
for c: u8 in needle { if haystack[j] != c { ret false; } j += 1; }
|
|
|
|
ret true;
|
|
|
|
}
|
|
|
|
let i: int = 0;
|
|
|
|
while i <= haystack_len - needle_len {
|
|
|
|
if match_at(haystack, needle, i) { ret i; }
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
ret -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: contains
|
|
|
|
|
|
|
|
Returns true if one string contains another
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
haystack - The string to look in
|
|
|
|
needle - The string to look for
|
|
|
|
*/
|
|
|
|
fn contains(haystack: str, needle: str) -> bool {
|
|
|
|
0 <= find(haystack, needle)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: starts_with
|
|
|
|
|
|
|
|
Returns true if one string starts with another
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
haystack - The string to look in
|
|
|
|
needle - The string to look for
|
|
|
|
*/
|
|
|
|
fn starts_with(haystack: str, needle: str) -> bool {
|
|
|
|
let haystack_len: uint = byte_len(haystack);
|
|
|
|
let needle_len: uint = byte_len(needle);
|
|
|
|
if needle_len == 0u { ret true; }
|
|
|
|
if needle_len > haystack_len { ret false; }
|
|
|
|
ret eq(substr(haystack, 0u, needle_len), needle);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: ends_with
|
|
|
|
|
|
|
|
Returns true if one string ends with another
|
|
|
|
|
|
|
|
haystack - The string to look in
|
|
|
|
needle - The string to look for
|
|
|
|
*/
|
|
|
|
fn ends_with(haystack: str, needle: str) -> bool {
|
|
|
|
let haystack_len: uint = byte_len(haystack);
|
|
|
|
let needle_len: uint = byte_len(needle);
|
|
|
|
ret if needle_len == 0u {
|
|
|
|
true
|
|
|
|
} else if needle_len > haystack_len {
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
eq(substr(haystack, haystack_len - needle_len, needle_len),
|
|
|
|
needle)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: substr
|
|
|
|
|
|
|
|
Take a substring of another. Returns a string containing `len` bytes
|
|
|
|
starting at byte offset `begin`.
|
|
|
|
|
|
|
|
This function is not unicode-safe.
|
|
|
|
|
|
|
|
Failure:
|
|
|
|
|
|
|
|
If `begin` + `len` is is greater than the byte length of the string
|
|
|
|
*/
|
|
|
|
fn substr(s: str, begin: uint, len: uint) -> str {
|
|
|
|
ret slice(s, begin, begin + len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: slice
|
|
|
|
|
|
|
|
Takes a bytewise slice from a string. Returns the substring from
|
|
|
|
[`begin`..`end`).
|
|
|
|
|
|
|
|
This function is not unicode-safe.
|
|
|
|
|
|
|
|
Failure:
|
|
|
|
|
|
|
|
- If begin is greater than end.
|
|
|
|
- If end is greater than the length of the string.
|
|
|
|
*/
|
|
|
|
fn slice(s: str, begin: uint, end: uint) -> str unsafe {
|
|
|
|
// FIXME: Typestate precondition
|
|
|
|
assert (begin <= end);
|
|
|
|
assert (end <= byte_len(s));
|
|
|
|
|
|
|
|
let v: [u8] = unsafe::reinterpret_cast(s);
|
|
|
|
let v2 = vec::slice(v, begin, end);
|
|
|
|
unsafe::leak(v);
|
|
|
|
v2 += [0u8];
|
|
|
|
let s2: str = unsafe::reinterpret_cast(v2);
|
|
|
|
unsafe::leak(v2);
|
|
|
|
ret s2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: safe_slice
|
|
|
|
*/
|
|
|
|
fn safe_slice(s: str, begin: uint, end: uint) : uint::le(begin, end) -> str {
|
|
|
|
// would need some magic to make this a precondition
|
|
|
|
assert (end <= byte_len(s));
|
|
|
|
ret slice(s, begin, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: shift_byte
|
|
|
|
|
|
|
|
Removes the first byte from a string and returns it.
|
|
|
|
|
|
|
|
This function is not unicode-safe.
|
|
|
|
*/
|
|
|
|
fn shift_byte(&s: str) -> u8 {
|
|
|
|
let len = byte_len(s);
|
|
|
|
assert (len > 0u);
|
|
|
|
let b = s[0];
|
|
|
|
s = substr(s, 1u, len - 1u);
|
|
|
|
ret b;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: pop_byte
|
|
|
|
|
|
|
|
Removes the last byte from a string and returns it.
|
|
|
|
|
|
|
|
This function is not unicode-safe.
|
|
|
|
*/
|
|
|
|
fn pop_byte(&s: str) -> u8 {
|
|
|
|
let len = byte_len(s);
|
|
|
|
assert (len > 0u);
|
|
|
|
let b = s[len - 1u];
|
|
|
|
s = substr(s, 0u, len - 1u);
|
|
|
|
ret b;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: push_byte
|
|
|
|
|
|
|
|
Appends a byte to a string.
|
|
|
|
|
|
|
|
This function is not unicode-safe.
|
|
|
|
*/
|
|
|
|
fn push_byte(&s: str, b: u8) { rustrt::rust_str_push(s, b); }
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: push_bytes
|
|
|
|
|
|
|
|
Appends a vector of bytes to a string.
|
|
|
|
|
|
|
|
This function is not unicode-safe.
|
|
|
|
*/
|
|
|
|
fn push_bytes(&s: str, bytes: [u8]) {
|
|
|
|
for byte in bytes { rustrt::rust_str_push(s, byte); }
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: split
|
|
|
|
|
|
|
|
Split a string at each occurance of a given separator
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
A vector containing all the strings between each occurance of the separator
|
2012-01-15 22:20:06 -06:00
|
|
|
|
|
|
|
FIXME: should be renamed to split_byte
|
2011-12-13 18:25:51 -06:00
|
|
|
*/
|
|
|
|
fn split(s: str, sep: u8) -> [str] {
|
|
|
|
let v: [str] = [];
|
|
|
|
let accum: str = "";
|
|
|
|
let ends_with_sep: bool = false;
|
|
|
|
for c: u8 in s {
|
|
|
|
if c == sep {
|
|
|
|
v += [accum];
|
|
|
|
accum = "";
|
|
|
|
ends_with_sep = true;
|
|
|
|
} else { accum += unsafe_from_byte(c); ends_with_sep = false; }
|
|
|
|
}
|
|
|
|
if byte_len(accum) != 0u || ends_with_sep { v += [accum]; }
|
|
|
|
ret v;
|
|
|
|
}
|
|
|
|
|
2011-12-19 12:08:36 -06:00
|
|
|
/*
|
|
|
|
Function: splitn
|
|
|
|
|
|
|
|
Split a string at each occurance of a given separator up to count times.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
A vector containing all the strings between each occurance of the separator
|
|
|
|
*/
|
|
|
|
fn splitn(s: str, sep: u8, count: uint) -> [str] {
|
|
|
|
let v = [];
|
|
|
|
let accum = "";
|
|
|
|
let n = count;
|
|
|
|
let ends_with_sep: bool = false;
|
|
|
|
for c in s {
|
|
|
|
if n > 0u && c == sep {
|
|
|
|
n -= 1u;
|
|
|
|
v += [accum];
|
|
|
|
accum = "";
|
|
|
|
ends_with_sep = true;
|
|
|
|
} else { accum += unsafe_from_byte(c); ends_with_sep = false; }
|
|
|
|
}
|
|
|
|
if byte_len(accum) != 0u || ends_with_sep { v += [accum]; }
|
|
|
|
ret v;
|
|
|
|
}
|
|
|
|
|
2011-12-15 18:56:33 -06:00
|
|
|
/*
|
|
|
|
Function: split_str
|
|
|
|
|
|
|
|
Splits a string at each occurrence of the given separator string. Empty
|
|
|
|
leading fields are suppressed, and empty trailing fields are preserved.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
A vector containing all the strings between each occurrence of the separator.
|
2012-01-15 22:20:06 -06:00
|
|
|
|
|
|
|
FIXME: should behave like split and split_char:
|
|
|
|
assert ["", "XXX", "YYY", ""] == split_str(".XXX.YYY.", ".");
|
2011-12-15 18:56:33 -06:00
|
|
|
*/
|
|
|
|
fn split_str(s: str, sep: str) -> [str] {
|
|
|
|
assert byte_len(sep) > 0u;
|
|
|
|
let v: [str] = [], accum = "", sep_match = 0u, leading = true;
|
|
|
|
for c: u8 in s {
|
|
|
|
// Did we match the entire separator?
|
|
|
|
if sep_match == byte_len(sep) {
|
|
|
|
if !leading { v += [accum]; }
|
|
|
|
accum = "";
|
|
|
|
sep_match = 0u;
|
|
|
|
}
|
|
|
|
|
|
|
|
if c == sep[sep_match] {
|
|
|
|
sep_match += 1u;
|
|
|
|
} else {
|
|
|
|
sep_match = 0u;
|
|
|
|
accum += unsafe_from_byte(c);
|
|
|
|
leading = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if byte_len(accum) > 0u { v += [accum]; }
|
|
|
|
if sep_match == byte_len(sep) { v += [""]; }
|
|
|
|
|
|
|
|
ret v;
|
|
|
|
}
|
|
|
|
|
2012-01-15 22:20:06 -06:00
|
|
|
/*
|
|
|
|
Function: split_func
|
|
|
|
|
|
|
|
Splits a string into substrings using a function
|
|
|
|
(unicode safe)
|
|
|
|
|
|
|
|
FIXME: will be renamed to split.
|
|
|
|
*/
|
|
|
|
fn split_func(ss: str, sepfn: fn&(cc: char)->bool) -> [str] {
|
|
|
|
let vv: [str] = [];
|
|
|
|
let accum: str = "";
|
|
|
|
let ends_with_sep: bool = false;
|
|
|
|
|
|
|
|
str::iter_chars(ss, {|cc| if sepfn(cc) {
|
|
|
|
vv += [accum];
|
|
|
|
accum = "";
|
|
|
|
ends_with_sep = true;
|
|
|
|
} else {
|
|
|
|
str::push_char(accum, cc);
|
|
|
|
ends_with_sep = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if char_len(accum) >= 0u || ends_with_sep {
|
|
|
|
vv += [accum];
|
|
|
|
}
|
|
|
|
|
|
|
|
ret vv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: split_char
|
|
|
|
|
|
|
|
Splits a string into a vector of the substrings separated by a given character
|
|
|
|
*/
|
|
|
|
fn split_char(ss: str, cc: char) -> [str] {
|
|
|
|
split_func(ss, {|kk| kk == cc})
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: lines
|
|
|
|
|
|
|
|
Splits a string into a vector of the substrings
|
|
|
|
separated by LF ('\n')
|
|
|
|
*/
|
|
|
|
fn lines(ss: str) -> [str] {
|
|
|
|
split_func(ss, {|cc| cc == '\n'})
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: lines_any
|
|
|
|
|
|
|
|
Splits a string into a vector of the substrings
|
|
|
|
separated by LF ('\n') and/or CR LF ('\r\n')
|
|
|
|
*/
|
|
|
|
fn lines_any(ss: str) -> [str] {
|
|
|
|
vec::map(lines(ss), {|s| trim_right(s)})
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: words
|
|
|
|
|
|
|
|
Splits a string into a vector of the substrings
|
|
|
|
separated by whitespace
|
|
|
|
*/
|
|
|
|
fn words(ss: str) -> [str] {
|
|
|
|
ret vec::filter( split_func(ss, {|cc| char::is_whitespace(cc)}),
|
|
|
|
{|w| 0u < str::char_len(w)});
|
|
|
|
}
|
|
|
|
|
2011-12-13 18:25:51 -06:00
|
|
|
/*
|
|
|
|
Function: concat
|
|
|
|
|
|
|
|
Concatenate a vector of strings
|
|
|
|
*/
|
|
|
|
fn concat(v: [str]) -> str {
|
|
|
|
let s: str = "";
|
|
|
|
for ss: str in v { s += ss; }
|
|
|
|
ret s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: connect
|
|
|
|
|
|
|
|
Concatenate a vector of strings, placing a given separator between each
|
|
|
|
*/
|
|
|
|
fn connect(v: [str], sep: str) -> str {
|
|
|
|
let s: str = "";
|
|
|
|
let first: bool = true;
|
|
|
|
for ss: str in v {
|
|
|
|
if first { first = false; } else { s += sep; }
|
|
|
|
s += ss;
|
|
|
|
}
|
|
|
|
ret s;
|
|
|
|
}
|
|
|
|
|
2012-01-01 10:30:12 -06:00
|
|
|
/*
|
|
|
|
Function: to_lower
|
|
|
|
|
|
|
|
Convert a string to lowercase
|
|
|
|
*/
|
|
|
|
fn to_lower(s: str) -> str {
|
|
|
|
let outstr = "";
|
|
|
|
iter_chars(s) { |c|
|
2012-01-02 14:29:44 -06:00
|
|
|
push_char(outstr, char::to_lower(c));
|
2012-01-01 10:30:12 -06:00
|
|
|
}
|
|
|
|
ret outstr;
|
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
/*
|
|
|
|
Function: to_upper
|
|
|
|
|
|
|
|
Convert a string to uppercase
|
|
|
|
*/
|
|
|
|
fn to_upper(s: str) -> str {
|
|
|
|
let outstr = "";
|
2012-01-01 10:30:12 -06:00
|
|
|
iter_chars(s) { |c|
|
2012-01-02 14:29:44 -06:00
|
|
|
push_char(outstr, char::to_upper(c));
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
|
|
|
ret outstr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: This is super-inefficient
|
|
|
|
/*
|
|
|
|
Function: replace
|
|
|
|
|
|
|
|
Replace all occurances of one string with another
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
s - The string containing substrings to replace
|
|
|
|
from - The string to replace
|
|
|
|
to - The replacement string
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
The original string with all occurances of `from` replaced with `to`
|
|
|
|
*/
|
|
|
|
fn replace(s: str, from: str, to: str) : is_not_empty(from) -> str {
|
|
|
|
// FIXME (694): Shouldn't have to check this
|
|
|
|
check (is_not_empty(from));
|
|
|
|
if byte_len(s) == 0u {
|
|
|
|
ret "";
|
|
|
|
} else if starts_with(s, from) {
|
|
|
|
ret to + replace(slice(s, byte_len(from), byte_len(s)), from, to);
|
|
|
|
} else {
|
2012-01-16 00:52:28 -06:00
|
|
|
let idx = find(s, from);
|
|
|
|
if idx == -1 {
|
|
|
|
ret s;
|
|
|
|
}
|
|
|
|
ret char_slice(s, 0u, idx as uint) + to +
|
|
|
|
replace(char_slice(s, idx as uint + char_len(from), char_len(s)),
|
|
|
|
from, to);
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Also not efficient
|
|
|
|
/*
|
|
|
|
Function: char_slice
|
|
|
|
|
|
|
|
Unicode-safe slice. Returns a slice of the given string containing
|
|
|
|
the characters in the range [`begin`..`end`). `begin` and `end` are
|
|
|
|
character indexes, not byte indexes.
|
|
|
|
|
|
|
|
Failure:
|
|
|
|
|
|
|
|
- If begin is greater than end
|
|
|
|
- If end is greater than the character length of the string
|
|
|
|
*/
|
|
|
|
fn char_slice(s: str, begin: uint, end: uint) -> str {
|
|
|
|
from_chars(vec::slice(to_chars(s), begin, end))
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: trim_left
|
|
|
|
|
|
|
|
Returns a string with leading whitespace removed.
|
|
|
|
*/
|
|
|
|
fn trim_left(s: str) -> str {
|
|
|
|
fn count_whities(s: [char]) -> uint {
|
|
|
|
let i = 0u;
|
|
|
|
while i < vec::len(s) {
|
|
|
|
if !char::is_whitespace(s[i]) { break; }
|
|
|
|
i += 1u;
|
|
|
|
}
|
|
|
|
ret i;
|
|
|
|
}
|
|
|
|
let chars = to_chars(s);
|
|
|
|
let whities = count_whities(chars);
|
|
|
|
ret from_chars(vec::slice(chars, whities, vec::len(chars)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: trim_right
|
|
|
|
|
|
|
|
Returns a string with trailing whitespace removed.
|
|
|
|
*/
|
|
|
|
fn trim_right(s: str) -> str {
|
|
|
|
fn count_whities(s: [char]) -> uint {
|
|
|
|
let i = vec::len(s);
|
|
|
|
while 0u < i {
|
|
|
|
if !char::is_whitespace(s[i - 1u]) { break; }
|
|
|
|
i -= 1u;
|
|
|
|
}
|
|
|
|
ret i;
|
|
|
|
}
|
|
|
|
let chars = to_chars(s);
|
|
|
|
let whities = count_whities(chars);
|
|
|
|
ret from_chars(vec::slice(chars, 0u, whities));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: trim
|
|
|
|
|
|
|
|
Returns a string with leading and trailing whitespace removed
|
|
|
|
*/
|
|
|
|
fn trim(s: str) -> str { trim_left(trim_right(s)) }
|
|
|
|
|
|
|
|
/*
|
|
|
|
Type: sbuf
|
|
|
|
|
|
|
|
An unsafe buffer of bytes. Corresponds to a C char pointer.
|
|
|
|
*/
|
|
|
|
type sbuf = *u8;
|
|
|
|
|
|
|
|
// NB: This is intentionally unexported because it's easy to misuse (there's
|
|
|
|
// no guarantee that the string is rooted). Instead, use as_buf below.
|
|
|
|
unsafe fn buf(s: str) -> sbuf {
|
|
|
|
let saddr = ptr::addr_of(s);
|
|
|
|
let vaddr: *[u8] = unsafe::reinterpret_cast(saddr);
|
|
|
|
let buf = vec::to_ptr(*vaddr);
|
|
|
|
ret buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: as_buf
|
|
|
|
|
|
|
|
Work with the byte buffer of a string. Allows for unsafe manipulation
|
|
|
|
of strings, which is useful for native interop.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
> let s = str::as_buf("PATH", { |path_buf| libc::getenv(path_buf) });
|
|
|
|
|
|
|
|
*/
|
|
|
|
fn as_buf<T>(s: str, f: block(sbuf) -> T) -> T unsafe {
|
|
|
|
let buf = buf(s); f(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-01-03 12:08:13 -06:00
|
|
|
Function: from_cstr
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
Create a Rust string from a null-terminated C string
|
|
|
|
*/
|
2012-01-03 12:08:13 -06:00
|
|
|
unsafe fn from_cstr(cstr: sbuf) -> str {
|
2011-12-13 18:25:51 -06:00
|
|
|
let res = "";
|
|
|
|
let start = cstr;
|
|
|
|
let curr = start;
|
|
|
|
let i = 0u;
|
|
|
|
while *curr != 0u8 {
|
|
|
|
push_byte(res, *curr);
|
|
|
|
i += 1u;
|
|
|
|
curr = ptr::offset(start, i);
|
|
|
|
}
|
|
|
|
ret res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: escape_char
|
|
|
|
|
|
|
|
Escapes a single character.
|
|
|
|
*/
|
|
|
|
fn escape_char(c: char) -> str {
|
|
|
|
alt c {
|
|
|
|
'"' { "\\\"" }
|
|
|
|
'\\' { "\\\\" }
|
|
|
|
'\n' { "\\n" }
|
|
|
|
'\t' { "\\t" }
|
|
|
|
'\r' { "\\r" }
|
|
|
|
// FIXME: uncomment this when extfmt is moved to core
|
|
|
|
// in a snapshot.
|
|
|
|
// '\x00' to '\x1f' { #fmt["\\x%02x", c as uint] }
|
|
|
|
v { from_char(c) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Function: escape
|
|
|
|
|
|
|
|
Escapes special characters inside the string, making it safe for transfer.
|
|
|
|
*/
|
|
|
|
fn escape(s: str) -> str {
|
|
|
|
let r = "";
|
|
|
|
loop_chars(s, { |c| r += escape_char(c); true });
|
|
|
|
r
|
|
|
|
}
|