Implement default values for at() (#995)
This commit is contained in:
parent
ca8462642a
commit
ffad8516af
@ -259,11 +259,14 @@ Fails with an error if the string is empty.
|
||||
- returns: any
|
||||
|
||||
### at()
|
||||
Extract the first grapheme cluster after the specified index. Fails with an
|
||||
error if the index is out of bounds.
|
||||
Extract the first grapheme cluster after the specified index. Returns the
|
||||
default value if the index is out of bounds or fails with an error if no default
|
||||
value was specified.
|
||||
|
||||
- index: integer (positional, required)
|
||||
The byte index.
|
||||
- default: any (named)
|
||||
A default value to return if the index is out of bounds.
|
||||
- returns: string
|
||||
|
||||
### slice()
|
||||
@ -450,10 +453,13 @@ Whether the content has the specified field.
|
||||
- returns: boolean
|
||||
|
||||
### at()
|
||||
Access the specified field on the content.
|
||||
Access the specified field on the content. Returns the default value if the
|
||||
field does not exist or fails with an error if no default value was specified.
|
||||
|
||||
- field: string (positional, required)
|
||||
The field to access.
|
||||
- default: any (named)
|
||||
A default value to return if the field does not exist.
|
||||
- returns: any
|
||||
|
||||
### location()
|
||||
@ -518,12 +524,14 @@ Fails with an error if the array is empty.
|
||||
- returns: any
|
||||
|
||||
### at()
|
||||
Returns the item at the specified index in the array.
|
||||
May be used on the left-hand side of an assignment.
|
||||
Fails with an error if the index is out of bounds.
|
||||
Returns the item at the specified index in the array. May be used on the
|
||||
left-hand side of an assignment. Returns the default value if the index is out
|
||||
of bounds or fails with an error if no default value was specified.
|
||||
|
||||
- index: integer (positional, required)
|
||||
The index at which to retrieve the item.
|
||||
- default: any (named)
|
||||
A default value to return if the index is out of bounds.
|
||||
- returns: any
|
||||
|
||||
### push()
|
||||
@ -738,13 +746,15 @@ The number of pairs in the dictionary.
|
||||
- returns: integer
|
||||
|
||||
### at()
|
||||
Returns the value associated with the specified key in the dictionary.
|
||||
May be used on the left-hand side of an assignment if the key is already
|
||||
present in the dictionary.
|
||||
Fails with an error if the key is not part of the dictionary.
|
||||
Returns the value associated with the specified key in the dictionary. May be
|
||||
used on the left-hand side of an assignment if the key is already present in the
|
||||
dictionary. Returns the default value if the key is not part of the dictionary
|
||||
or fails with an error if no default value was specified.
|
||||
|
||||
- key: string (positional, required)
|
||||
The key at which to retrieve the item.
|
||||
- default: any (named)
|
||||
A default value to return if the key is not part of the dictionary.
|
||||
- returns: any
|
||||
|
||||
### insert()
|
||||
|
@ -74,10 +74,15 @@ impl Array {
|
||||
}
|
||||
|
||||
/// Borrow the value at the given index.
|
||||
pub fn at(&self, index: i64) -> StrResult<&Value> {
|
||||
pub fn at<'a>(
|
||||
&'a self,
|
||||
index: i64,
|
||||
default: Option<&'a Value>,
|
||||
) -> StrResult<&'a Value> {
|
||||
self.locate(index)
|
||||
.and_then(|i| self.0.get(i))
|
||||
.ok_or_else(|| out_of_bounds(index, self.len()))
|
||||
.or(default)
|
||||
.ok_or_else(|| out_of_bounds_no_default(index, self.len()))
|
||||
}
|
||||
|
||||
/// Mutably borrow the value at the given index.
|
||||
@ -85,7 +90,7 @@ impl Array {
|
||||
let len = self.len();
|
||||
self.locate(index)
|
||||
.and_then(move |i| self.0.make_mut().get_mut(i))
|
||||
.ok_or_else(|| out_of_bounds(index, len))
|
||||
.ok_or_else(|| out_of_bounds_no_default(index, len))
|
||||
}
|
||||
|
||||
/// Push a value to the end of the array.
|
||||
@ -462,3 +467,14 @@ fn array_is_empty() -> EcoString {
|
||||
fn out_of_bounds(index: i64, len: i64) -> EcoString {
|
||||
eco_format!("array index out of bounds (index: {}, len: {})", index, len)
|
||||
}
|
||||
|
||||
/// The out of bounds access error message when no default value was given.
|
||||
#[cold]
|
||||
fn out_of_bounds_no_default(index: i64, len: i64) -> EcoString {
|
||||
eco_format!(
|
||||
"array index out of bounds (index: {}, len: {}) \
|
||||
and no default value was specified",
|
||||
index,
|
||||
len
|
||||
)
|
||||
}
|
||||
|
@ -53,16 +53,20 @@ impl Dict {
|
||||
self.0.len() as i64
|
||||
}
|
||||
|
||||
/// Borrow the value the given `key` maps to.
|
||||
pub fn at(&self, key: &str) -> StrResult<&Value> {
|
||||
self.0.get(key).ok_or_else(|| missing_key(key))
|
||||
/// Borrow the value the given `key` maps to,
|
||||
pub fn at<'a>(
|
||||
&'a self,
|
||||
key: &str,
|
||||
default: Option<&'a Value>,
|
||||
) -> StrResult<&'a Value> {
|
||||
self.0.get(key).or(default).ok_or_else(|| missing_key_no_default(key))
|
||||
}
|
||||
|
||||
/// Mutably borrow the value the given `key` maps to.
|
||||
pub fn at_mut(&mut self, key: &str) -> StrResult<&mut Value> {
|
||||
Arc::make_mut(&mut self.0)
|
||||
.get_mut(key)
|
||||
.ok_or_else(|| missing_key(key))
|
||||
.ok_or_else(|| missing_key_no_default(key))
|
||||
}
|
||||
|
||||
/// Remove the value if the dictionary contains the given key.
|
||||
@ -218,3 +222,13 @@ impl<'a> IntoIterator for &'a Dict {
|
||||
fn missing_key(key: &str) -> EcoString {
|
||||
eco_format!("dictionary does not contain key {:?}", Str::from(key))
|
||||
}
|
||||
|
||||
/// The missing key access error message when no default was fiven.
|
||||
#[cold]
|
||||
fn missing_key_no_default(key: &str) -> EcoString {
|
||||
eco_format!(
|
||||
"dictionary does not contain key {:?} \
|
||||
and no default value was specified",
|
||||
Str::from(key)
|
||||
)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ 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")?).at(span)?),
|
||||
"at" => Value::Str(string.at(args.expect("index")?, None).at(span)?),
|
||||
"slice" => {
|
||||
let start = args.expect("start")?;
|
||||
let mut end = args.eat()?;
|
||||
@ -73,7 +73,7 @@ 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")?).at(span)?,
|
||||
"at" => content.at(&args.expect::<EcoString>("field")?, None).at(span)?,
|
||||
"location" => content
|
||||
.location()
|
||||
.ok_or("this method can only be called on content returned by query(..)")
|
||||
@ -86,7 +86,10 @@ pub fn call(
|
||||
"len" => Value::Int(array.len()),
|
||||
"first" => array.first().at(span)?.clone(),
|
||||
"last" => array.last().at(span)?.clone(),
|
||||
"at" => array.at(args.expect("index")?).at(span)?.clone(),
|
||||
"at" => array
|
||||
.at(args.expect("index")?, args.named("default")?.as_ref())
|
||||
.at(span)?
|
||||
.clone(),
|
||||
"slice" => {
|
||||
let start = args.expect("start")?;
|
||||
let mut end = args.eat()?;
|
||||
@ -125,7 +128,10 @@ pub fn call(
|
||||
|
||||
Value::Dict(dict) => match method {
|
||||
"len" => Value::Int(dict.len()),
|
||||
"at" => dict.at(&args.expect::<Str>("key")?).at(span)?.clone(),
|
||||
"at" => dict
|
||||
.at(&args.expect::<Str>("key")?, args.named("default")?.as_ref())
|
||||
.at(span)?
|
||||
.clone(),
|
||||
"keys" => Value::Array(dict.keys()),
|
||||
"values" => Value::Array(dict.values()),
|
||||
"pairs" => Value::Array(dict.pairs()),
|
||||
|
@ -1255,7 +1255,7 @@ impl ast::Pattern {
|
||||
for p in destruct.bindings() {
|
||||
match p {
|
||||
ast::DestructuringKind::Normal(expr) => {
|
||||
let Ok(v) = value.at(i) else {
|
||||
let Ok(v) = value.at(i, None) else {
|
||||
bail!(expr.span(), "not enough elements to destructure");
|
||||
};
|
||||
f(vm, expr, v.clone())?;
|
||||
@ -1310,17 +1310,17 @@ impl ast::Pattern {
|
||||
for p in destruct.bindings() {
|
||||
match p {
|
||||
ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => {
|
||||
let Ok(v) = value.at(&ident) else {
|
||||
bail!(ident.span(), "destructuring key not found in dictionary");
|
||||
};
|
||||
let Ok(v) = value.at(&ident, None) else {
|
||||
bail!(ident.span(), "destructuring key not found in dictionary");
|
||||
};
|
||||
f(vm, ast::Expr::Ident(ident.clone()), v.clone())?;
|
||||
used.insert(ident.take());
|
||||
}
|
||||
ast::DestructuringKind::Sink(spread) => sink = spread.expr(),
|
||||
ast::DestructuringKind::Named(named) => {
|
||||
let Ok(v) = value.at(named.name().as_str()) else {
|
||||
bail!(named.name().span(), "destructuring key not found in dictionary");
|
||||
};
|
||||
let Ok(v) = value.at(named.name().as_str(), None) else {
|
||||
bail!(named.name().span(), "destructuring key not found in dictionary");
|
||||
};
|
||||
f(vm, named.expr(), v.clone())?;
|
||||
used.insert(named.name().take());
|
||||
}
|
||||
|
@ -69,12 +69,13 @@ impl Str {
|
||||
}
|
||||
|
||||
/// Extract the grapheme cluster at the given index.
|
||||
pub fn at(&self, index: i64) -> StrResult<Self> {
|
||||
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()
|
||||
.ok_or_else(|| out_of_bounds(index, len))?;
|
||||
.or(default)
|
||||
.ok_or_else(|| no_default_and_out_of_bounds(index, len))?;
|
||||
Ok(grapheme.into())
|
||||
}
|
||||
|
||||
@ -348,6 +349,12 @@ fn out_of_bounds(index: i64, len: i64) -> EcoString {
|
||||
eco_format!("string index out of bounds (index: {}, len: {})", index, len)
|
||||
}
|
||||
|
||||
/// The out of bounds access error message when no default value was given.
|
||||
#[cold]
|
||||
fn no_default_and_out_of_bounds(index: i64, len: i64) -> EcoString {
|
||||
eco_format!("no default value was specified and string index out of bounds (index: {}, len: {})", index, len)
|
||||
}
|
||||
|
||||
/// The char boundary access error message.
|
||||
#[cold]
|
||||
fn not_a_char_boundary(index: i64) -> EcoString {
|
||||
|
@ -124,8 +124,8 @@ impl Value {
|
||||
pub fn field(&self, field: &str) -> StrResult<Value> {
|
||||
match self {
|
||||
Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol),
|
||||
Self::Dict(dict) => dict.at(field).cloned(),
|
||||
Self::Content(content) => content.at(field),
|
||||
Self::Dict(dict) => dict.at(field, None).cloned(),
|
||||
Self::Content(content) => content.at(field, None),
|
||||
Self::Module(module) => module.get(field).cloned(),
|
||||
v => Err(eco_format!("cannot access fields on type {}", v.type_name())),
|
||||
}
|
||||
|
@ -252,8 +252,10 @@ impl Content {
|
||||
}
|
||||
|
||||
/// Borrow the value of the given field.
|
||||
pub fn at(&self, field: &str) -> StrResult<Value> {
|
||||
self.field(field).ok_or_else(|| missing_field(field))
|
||||
pub fn at(&self, field: &str, default: Option<Value>) -> StrResult<Value> {
|
||||
self.field(field)
|
||||
.or(default)
|
||||
.ok_or_else(|| missing_field_no_default(field))
|
||||
}
|
||||
|
||||
/// The content's label.
|
||||
@ -582,8 +584,12 @@ impl Fold for Vec<Meta> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The missing key access error message.
|
||||
/// The missing key access error message when no default value was given.
|
||||
#[cold]
|
||||
fn missing_field(key: &str) -> EcoString {
|
||||
eco_format!("content does not contain field {:?}", Str::from(key))
|
||||
fn missing_field_no_default(key: &str) -> EcoString {
|
||||
eco_format!(
|
||||
"content does not contain field {:?} and \
|
||||
no default value was specified",
|
||||
Str::from(key)
|
||||
)
|
||||
}
|
||||
|
@ -47,17 +47,22 @@
|
||||
|
||||
---
|
||||
// Test rvalue out of bounds.
|
||||
// Error: 2-17 array index out of bounds (index: 5, len: 3)
|
||||
// Error: 2-17 array index out of bounds (index: 5, len: 3) and no default value was specified
|
||||
#(1, 2, 3).at(5)
|
||||
|
||||
---
|
||||
// Test lvalue out of bounds.
|
||||
#{
|
||||
let array = (1, 2, 3)
|
||||
// Error: 3-14 array index out of bounds (index: 3, len: 3)
|
||||
// Error: 3-14 array index out of bounds (index: 3, len: 3) and no default value was specified
|
||||
array.at(3) = 5
|
||||
}
|
||||
|
||||
---
|
||||
// Test default value.
|
||||
#test((1, 2, 3).at(2, default: 5), 3)
|
||||
#test((1, 2, 3).at(3, default: 5), 5)
|
||||
|
||||
---
|
||||
// Test bad lvalue.
|
||||
// Error: 2:3-2:14 cannot mutate a temporary value
|
||||
@ -243,7 +248,7 @@
|
||||
#([Hi], [There]).sorted()
|
||||
|
||||
---
|
||||
// Error: 2-18 array index out of bounds (index: -4, len: 3)
|
||||
// Error: 2-18 array index out of bounds (index: -4, len: 3) and no default value was specified
|
||||
#(1, 2, 3).at(-4)
|
||||
|
||||
---
|
||||
|
@ -31,15 +31,20 @@
|
||||
// Test rvalue missing key.
|
||||
#{
|
||||
let dict = (a: 1, b: 2)
|
||||
// Error: 11-23 dictionary does not contain key "c"
|
||||
// Error: 11-23 dictionary does not contain key "c" and no default value was specified
|
||||
let x = dict.at("c")
|
||||
}
|
||||
|
||||
---
|
||||
// Test default value.
|
||||
#test((a: 1, b: 2).at("b", default: 3), 2)
|
||||
#test((a: 1, b: 2).at("c", default: 3), 3)
|
||||
|
||||
---
|
||||
// Missing lvalue is not automatically none-initialized.
|
||||
#{
|
||||
let dict = (:)
|
||||
// Error: 3-9 dictionary does not contain key "b"
|
||||
// Error: 3-9 dictionary does not contain key "b" and no default value was specified
|
||||
dict.b += 1
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
- C
|
||||
|
||||
---
|
||||
// Error: 6-13 dictionary does not contain key "invalid"
|
||||
// Error: 6-13 dictionary does not contain key "invalid" and no default value was specified
|
||||
#(:).invalid
|
||||
|
||||
---
|
||||
@ -31,7 +31,7 @@
|
||||
#false.ok
|
||||
|
||||
---
|
||||
// Error: 25-28 content does not contain field "fun"
|
||||
// Error: 25-28 content does not contain field "fun" and no default value was specified
|
||||
#show heading: it => it.fun
|
||||
= A
|
||||
|
||||
|
@ -78,7 +78,7 @@ Another text.
|
||||
= Heading
|
||||
|
||||
---
|
||||
// Error: 25-29 content does not contain field "page"
|
||||
// Error: 25-29 content does not contain field "page" and no default value was specified
|
||||
#show heading: it => it.page
|
||||
= Heading
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
||||
#"🏳️🌈".at(2)
|
||||
|
||||
---
|
||||
// Error: 2-15 string index out of bounds (index: 5, len: 5)
|
||||
// Error: 2-15 no default value was specified and string index out of bounds (index: 5, len: 5)
|
||||
#"Hello".at(5)
|
||||
|
||||
---
|
||||
|
Loading…
x
Reference in New Issue
Block a user