diff options
| author | Michael Lohr <michael@lohr.dev> | 2023-05-03 12:34:35 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-03 12:34:35 +0200 |
| commit | ffad8516af0b91121dc0761c8026e0a12939a7d4 (patch) | |
| tree | ada5c6b5510b5f509997ccf5308a9cafc8618990 /src/eval | |
| parent | ca8462642a96ec282afeb0774b6d5daf546ac236 (diff) | |
Implement default values for at() (#995)
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/array.rs | 22 | ||||
| -rw-r--r-- | src/eval/dict.rs | 22 | ||||
| -rw-r--r-- | src/eval/methods.rs | 14 | ||||
| -rw-r--r-- | src/eval/mod.rs | 14 | ||||
| -rw-r--r-- | src/eval/str.rs | 11 | ||||
| -rw-r--r-- | src/eval/value.rs | 4 |
6 files changed, 65 insertions, 22 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())), } |
