summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Lohr <michael@lohr.dev>2023-05-03 12:34:35 +0200
committerGitHub <noreply@github.com>2023-05-03 12:34:35 +0200
commitffad8516af0b91121dc0761c8026e0a12939a7d4 (patch)
treeada5c6b5510b5f509997ccf5308a9cafc8618990 /src
parentca8462642a96ec282afeb0774b6d5daf546ac236 (diff)
Implement default values for at() (#995)
Diffstat (limited to 'src')
-rw-r--r--src/eval/array.rs22
-rw-r--r--src/eval/dict.rs22
-rw-r--r--src/eval/methods.rs14
-rw-r--r--src/eval/mod.rs14
-rw-r--r--src/eval/str.rs11
-rw-r--r--src/eval/value.rs4
-rw-r--r--src/model/content.rs16
7 files changed, 76 insertions, 27 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs
index f6e2f2d4..6ae5d7cf 100644
--- a/src/eval/array.rs
+++ b/src/eval/array.rs
@@ -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
+ )
+}
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index b137f03c..1b28a6ba 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -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)
+ )
+}
diff --git a/src/eval/methods.rs b/src/eval/methods.rs
index 6cbb846b..8d042a5c 100644
--- a/src/eval/methods.rs
+++ b/src/eval/methods.rs
@@ -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()),
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index fe1fac3b..b430b400 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -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());
}
diff --git a/src/eval/str.rs b/src/eval/str.rs
index 89be3699..d7e00bf6 100644
--- a/src/eval/str.rs
+++ b/src/eval/str.rs
@@ -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 {
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 0548c01f..1bfad9c8 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -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())),
}
diff --git a/src/model/content.rs b/src/model/content.rs
index 4af4e655..1bd19f14 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -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)
+ )
}