Implement default values for at() (#995)

This commit is contained in:
Michael Lohr 2023-05-03 12:34:35 +02:00 committed by GitHub
parent ca8462642a
commit ffad8516af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 115 additions and 46 deletions

View File

@ -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()

View File

@ -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
)
}

View File

@ -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)
)
}

View File

@ -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()),

View File

@ -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());
}

View File

@ -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 {

View File

@ -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())),
}

View File

@ -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)
)
}

View File

@ -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)
---

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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)
---