summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPg Biel <9021226+PgBiel@users.noreply.github.com>2023-05-29 15:15:32 -0300
committerGitHub <noreply@github.com>2023-05-29 20:15:32 +0200
commite4557f66399daa848ce1b7cf6cd74b3dd03e74e5 (patch)
tree91c5cac79fa445460362cd0f7a0bfdd60e2cd1ea
parent31dfe32242ef7ab8304874fd0260d27649880df8 (diff)
Fix `.at(default: ...)` for strings and content (#1339)
-rw-r--r--src/eval/methods.rs10
-rw-r--r--src/eval/str.rs22
-rw-r--r--tests/typ/compiler/methods.typ4
-rw-r--r--tests/typ/compiler/string.typ8
4 files changed, 34 insertions, 10 deletions
diff --git a/src/eval/methods.rs b/src/eval/methods.rs
index 42084543..7de3bc20 100644
--- a/src/eval/methods.rs
+++ b/src/eval/methods.rs
@@ -31,7 +31,11 @@ pub fn call(
"len" => Value::Int(string.len()),
"first" => Value::Str(string.first().at(span)?),
"last" => Value::Str(string.last().at(span)?),
- "at" => Value::Str(string.at(args.expect("index")?, None).at(span)?),
+ "at" => {
+ let index = args.expect("index")?;
+ let default = args.named::<EcoString>("default")?;
+ Value::Str(string.at(index, default.as_deref()).at(span)?)
+ }
"slice" => {
let start = args.expect("start")?;
let mut end = args.eat()?;
@@ -74,7 +78,9 @@ pub fn call(
Value::Content(content) => match method {
"func" => content.func().into(),
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
- "at" => content.at(&args.expect::<EcoString>("field")?, None).at(span)?,
+ "at" => content
+ .at(&args.expect::<EcoString>("field")?, args.named("default")?)
+ .at(span)?,
"location" => content
.location()
.ok_or("this method can only be called on content returned by query(..)")
diff --git a/src/eval/str.rs b/src/eval/str.rs
index d7e00bf6..3c377595 100644
--- a/src/eval/str.rs
+++ b/src/eval/str.rs
@@ -71,9 +71,9 @@ impl Str {
/// Extract the grapheme cluster at the given index.
pub fn at<'a>(&'a self, index: i64, default: Option<&'a str>) -> StrResult<Self> {
let len = self.len();
- let grapheme = self.0[self.locate(index)?..]
- .graphemes(true)
- .next()
+ let grapheme = self
+ .locate_opt(index)?
+ .and_then(|i| self.0[i..].graphemes(true).next())
.or(default)
.ok_or_else(|| no_default_and_out_of_bounds(index, len))?;
Ok(grapheme.into())
@@ -325,22 +325,28 @@ impl Str {
Ok(Self(self.0.repeat(n)))
}
- /// Resolve an index.
- fn locate(&self, index: i64) -> StrResult<usize> {
+ /// Resolve an index, if it is within bounds.
+ /// Errors on invalid char boundaries.
+ fn locate_opt(&self, index: i64) -> StrResult<Option<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()))?;
+ .filter(|&v| v <= self.0.len());
- if !self.0.is_char_boundary(resolved) {
+ if resolved.map_or(false, |i| !self.0.is_char_boundary(i)) {
return Err(not_a_char_boundary(index));
}
Ok(resolved)
}
+
+ /// Resolve an index or throw an out of bounds error.
+ fn locate(&self, index: i64) -> StrResult<usize> {
+ self.locate_opt(index)?
+ .ok_or_else(|| out_of_bounds(index, self.len()))
+ }
}
/// The out of bounds access error message.
diff --git a/tests/typ/compiler/methods.typ b/tests/typ/compiler/methods.typ
index afcb024f..8b36dea9 100644
--- a/tests/typ/compiler/methods.typ
+++ b/tests/typ/compiler/methods.typ
@@ -27,6 +27,10 @@
}
---
+// Test .at() default values for content.
+#test(auto, [a].at("doesn't exist", default: auto))
+
+---
// Error: 2:2-2:15 type array has no method `fun`
#let numbers = ()
#numbers.fun()
diff --git a/tests/typ/compiler/string.typ b/tests/typ/compiler/string.typ
index 9a4b4146..c4c1669e 100644
--- a/tests/typ/compiler/string.typ
+++ b/tests/typ/compiler/string.typ
@@ -29,6 +29,10 @@
#test("Hey: πŸ³οΈβ€πŸŒˆ there!".at(5), "πŸ³οΈβ€πŸŒˆ")
---
+// Test `at`'s 'default' parameter.
+#test("z", "Hello".at(5, default: "z"))
+
+---
// Error: 2-14 string index 2 is not a character boundary
#"πŸ³οΈβ€πŸŒˆ".at(2)
@@ -37,6 +41,10 @@
#"Hello".at(5)
---
+// Error: 25-32 expected string, found dictionary
+#"Hello".at(5, default: (a: 10))
+
+---
// Test the `slice` method.
#test("abc".slice(1, 2), "b")
#test("abc🏑def".slice(2, 7), "c🏑")