mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Check char boundaries in string methods
This commit is contained in:
parent
56b6a2a908
commit
585f656487
@ -68,38 +68,20 @@ impl Str {
|
|||||||
/// Extract the grapheme cluster at the given index.
|
/// Extract the grapheme cluster at the given index.
|
||||||
pub fn at(&self, index: i64) -> StrResult<Self> {
|
pub fn at(&self, index: i64) -> StrResult<Self> {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
let grapheme = self
|
let grapheme = self.0[self.locate(index)?..]
|
||||||
.locate(index)
|
.graphemes(true)
|
||||||
.filter(|&index| index <= self.0.len())
|
.next()
|
||||||
.and_then(|index| self.0[index..].graphemes(true).next())
|
|
||||||
.ok_or_else(|| out_of_bounds(index, len))?;
|
.ok_or_else(|| out_of_bounds(index, len))?;
|
||||||
Ok(grapheme.into())
|
Ok(grapheme.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a contigous substring.
|
/// Extract a contigous substring.
|
||||||
pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> {
|
pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> {
|
||||||
let len = self.len();
|
let start = self.locate(start)?;
|
||||||
let start = self
|
let end = self.locate(end.unwrap_or(self.len()))?.max(start);
|
||||||
.locate(start)
|
|
||||||
.filter(|&start| start <= self.0.len())
|
|
||||||
.ok_or_else(|| out_of_bounds(start, len))?;
|
|
||||||
|
|
||||||
let end = end.unwrap_or(self.len());
|
|
||||||
let end = self
|
|
||||||
.locate(end)
|
|
||||||
.filter(|&end| end <= self.0.len())
|
|
||||||
.ok_or_else(|| out_of_bounds(end, len))?
|
|
||||||
.max(start);
|
|
||||||
|
|
||||||
Ok(self.0[start..end].into())
|
Ok(self.0[start..end].into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve an index.
|
|
||||||
fn locate(&self, index: i64) -> Option<usize> {
|
|
||||||
usize::try_from(if index >= 0 { index } else { self.len().checked_add(index)? })
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the given pattern exists in this string.
|
/// Whether the given pattern exists in this string.
|
||||||
pub fn contains(&self, pattern: StrPattern) -> bool {
|
pub fn contains(&self, pattern: StrPattern) -> bool {
|
||||||
match pattern {
|
match pattern {
|
||||||
@ -286,12 +268,35 @@ impl Str {
|
|||||||
|
|
||||||
Ok(Self(self.0.repeat(n)))
|
Ok(Self(self.0.repeat(n)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve an index.
|
||||||
|
fn locate(&self, index: i64) -> StrResult<usize> {
|
||||||
|
let wrapped =
|
||||||
|
if index >= 0 { Some(index) } else { self.len().checked_add(index) };
|
||||||
|
|
||||||
|
let resolved = wrapped
|
||||||
|
.and_then(|v| usize::try_from(v).ok())
|
||||||
|
.filter(|&v| v <= self.0.len())
|
||||||
|
.ok_or_else(|| out_of_bounds(index, self.len()))?;
|
||||||
|
|
||||||
|
if !self.0.is_char_boundary(resolved) {
|
||||||
|
return Err(not_a_char_boundary(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(resolved)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The out of bounds access error message.
|
/// The out of bounds access error message.
|
||||||
#[cold]
|
#[cold]
|
||||||
fn out_of_bounds(index: i64, len: i64) -> String {
|
fn out_of_bounds(index: i64, len: i64) -> EcoString {
|
||||||
format!("string index out of bounds (index: {}, len: {})", index, len)
|
format_eco!("string index out of bounds (index: {}, len: {})", index, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The char boundary access error message.
|
||||||
|
#[cold]
|
||||||
|
fn not_a_char_boundary(index: i64) -> EcoString {
|
||||||
|
format_eco!("string index {} is not a character boundary", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error message when the string is empty.
|
/// The error message when the string is empty.
|
||||||
|
@ -27,8 +27,12 @@
|
|||||||
#test("Hey: 🏳️🌈 there!".at(5), "🏳️🌈")
|
#test("Hey: 🏳️🌈 there!".at(5), "🏳️🌈")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 4-17 string index out of bounds (index: 5, len: 5)
|
// Error: 2-14 string index 2 is not a character boundary
|
||||||
#{ "Hello".at(5) }
|
#"🏳️🌈".at(2)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 2-15 string index out of bounds (index: 5, len: 5)
|
||||||
|
#"Hello".at(5)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `slice` method.
|
// Test the `slice` method.
|
||||||
@ -37,6 +41,10 @@
|
|||||||
#test("abc🏡def".slice(2, -2), "c🏡d")
|
#test("abc🏡def".slice(2, -2), "c🏡d")
|
||||||
#test("abc🏡def".slice(-3, -1), "de")
|
#test("abc🏡def".slice(-3, -1), "de")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 2-21 string index -1 is not a character boundary
|
||||||
|
#"🏳️🌈".slice(0, -1)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `contains` method.
|
// Test the `contains` method.
|
||||||
#test("abc".contains("b"), true)
|
#test("abc".contains("b"), true)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user