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